diff options
author | Julien HENRY <julien.henry@sonarsource.com> | 2016-03-17 17:05:56 +0100 |
---|---|---|
committer | Julien HENRY <julien.henry@sonarsource.com> | 2016-03-18 09:35:05 +0100 |
commit | cfcbe278f7ced12599d898d50f3fe68bfbf95155 (patch) | |
tree | 5b4116ad08a8ba87ffc5bf9f159a431b9609b48f /sonar-scanner-engine/src | |
parent | 38ce80934961773a9a35ec0401994b3d726597dc (diff) | |
download | sonarqube-cfcbe278f7ced12599d898d50f3fe68bfbf95155.tar.gz sonarqube-cfcbe278f7ced12599d898d50f3fe68bfbf95155.zip |
Rename batch into scanner
Diffstat (limited to 'sonar-scanner-engine/src')
625 files changed, 50990 insertions, 0 deletions
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/DefaultFileLinesContext.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/DefaultFileLinesContext.java new file mode 100644 index 00000000000..a4045f18ce0 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/DefaultFileLinesContext.java @@ -0,0 +1,156 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch; + +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import java.util.Map; +import org.sonar.api.batch.SonarIndex; +import org.sonar.api.measures.FileLinesContext; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.Metric; +import org.sonar.api.measures.PersistenceMode; +import org.sonar.api.resources.Resource; +import org.sonar.api.resources.ResourceUtils; +import org.sonar.api.utils.KeyValueFormat; +import org.sonar.api.utils.KeyValueFormat.Converter; + +public class DefaultFileLinesContext implements FileLinesContext { + + private final SonarIndex index; + private final Resource resource; + + /** + * metric key -> line -> value + */ + private final Map<String, Map<Integer, Object>> map = Maps.newHashMap(); + + public DefaultFileLinesContext(SonarIndex index, Resource resource) { + Preconditions.checkNotNull(index); + Preconditions.checkArgument(ResourceUtils.isFile(resource)); + this.index = index; + this.resource = resource; + } + + @Override + public void setIntValue(String metricKey, int line, int value) { + Preconditions.checkNotNull(metricKey); + Preconditions.checkArgument(line > 0); + + setValue(metricKey, line, value); + } + + @Override + public Integer getIntValue(String metricKey, int line) { + Preconditions.checkNotNull(metricKey); + Preconditions.checkArgument(line > 0); + + Map lines = map.get(metricKey); + if (lines == null) { + // not in memory, so load + lines = loadData(metricKey, KeyValueFormat.newIntegerConverter()); + map.put(metricKey, lines); + } + return (Integer) lines.get(line); + } + + @Override + public void setStringValue(String metricKey, int line, String value) { + Preconditions.checkNotNull(metricKey); + Preconditions.checkArgument(line > 0); + Preconditions.checkNotNull(value); + + setValue(metricKey, line, value); + } + + @Override + public String getStringValue(String metricKey, int line) { + Preconditions.checkNotNull(metricKey); + Preconditions.checkArgument(line > 0); + + Map lines = map.get(metricKey); + if (lines == null) { + // not in memory, so load + lines = loadData(metricKey, KeyValueFormat.newStringConverter()); + map.put(metricKey, lines); + } + return (String) lines.get(line); + } + + private Map<Integer, Object> getOrCreateLines(String metricKey) { + Map<Integer, Object> lines = map.get(metricKey); + if (lines == null) { + lines = Maps.newHashMap(); + map.put(metricKey, lines); + } + return lines; + } + + private void setValue(String metricKey, int line, Object value) { + getOrCreateLines(metricKey).put(line, value); + } + + @Override + public void save() { + for (Map.Entry<String, Map<Integer, Object>> entry : map.entrySet()) { + String metricKey = entry.getKey(); + Map<Integer, Object> lines = entry.getValue(); + if (shouldSave(lines)) { + String data = KeyValueFormat.format(lines); + Measure measure = new Measure(metricKey) + .setPersistenceMode(PersistenceMode.DATABASE) + .setData(data); + index.addMeasure(resource, measure); + entry.setValue(ImmutableMap.copyOf(lines)); + } + } + } + + private Map loadData(String metricKey, Converter converter) { + // FIXME no way to load measure only by key + Measure measure = index.getMeasure(resource, new Metric(metricKey)); + String data = measure != null ? measure.getData() : null; + if (data != null) { + return ImmutableMap.copyOf(KeyValueFormat.parse(data, KeyValueFormat.newIntegerConverter(), converter)); + } + // no such measure + return ImmutableMap.of(); + } + + /** + * Checks that measure was not saved. + * + * @see #loadData(String, Converter) + * @see #save() + */ + private static boolean shouldSave(Map<Integer, Object> lines) { + return !(lines instanceof ImmutableMap); + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("map", map) + .toString(); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/DefaultFileLinesContextFactory.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/DefaultFileLinesContextFactory.java new file mode 100644 index 00000000000..922f101f085 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/DefaultFileLinesContextFactory.java @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch; + +import org.sonar.api.batch.SonarIndex; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.measures.FileLinesContext; +import org.sonar.api.measures.FileLinesContextFactory; +import org.sonar.api.resources.File; +import org.sonar.api.resources.Resource; + +public class DefaultFileLinesContextFactory implements FileLinesContextFactory { + + private final SonarIndex index; + + public DefaultFileLinesContextFactory(SonarIndex index) { + this.index = index; + } + + @Override + public FileLinesContext createFor(Resource model) { + // Reload resource in case it use deprecated key + Resource resource = index.getResource(model); + return new DefaultFileLinesContext(index, resource); + } + + @Override + public FileLinesContext createFor(InputFile inputFile) { + File sonarFile = File.create(inputFile.relativePath()); + // Reload resource from index + sonarFile = index.getResource(sonarFile); + return new DefaultFileLinesContext(index, sonarFile); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/DefaultProjectTree.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/DefaultProjectTree.java new file mode 100644 index 00000000000..5b56f27154c --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/DefaultProjectTree.java @@ -0,0 +1,101 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import org.apache.commons.lang.ObjectUtils; +import org.picocontainer.Startable; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.resources.Project; +import org.sonar.batch.scan.ImmutableProjectReactor; + +public class DefaultProjectTree implements Startable { + + private final ProjectConfigurator configurator; + private final ImmutableProjectReactor projectReactor; + + private List<Project> projects; + private Map<ProjectDefinition, Project> projectsByDef; + + public DefaultProjectTree(ImmutableProjectReactor projectReactor, ProjectConfigurator projectConfigurator) { + this.projectReactor = projectReactor; + this.configurator = projectConfigurator; + } + + @Override + public void start() { + doStart(projectReactor.getProjects()); + } + + @Override + public void stop() { + // Nothing to do + } + + void doStart(Collection<ProjectDefinition> definitions) { + projects = Lists.newArrayList(); + projectsByDef = Maps.newHashMap(); + + for (ProjectDefinition def : definitions) { + Project project = configurator.create(def); + projectsByDef.put(def, project); + projects.add(project); + } + + for (Map.Entry<ProjectDefinition, Project> entry : projectsByDef.entrySet()) { + ProjectDefinition def = entry.getKey(); + Project project = entry.getValue(); + for (ProjectDefinition module : def.getSubProjects()) { + projectsByDef.get(module).setParent(project); + } + } + + // Configure + for (Project project : projects) { + configurator.configure(project); + } + } + + public List<Project> getProjects() { + return projects; + } + + public Project getRootProject() { + for (Project project : projects) { + if (project.getParent() == null) { + return project; + } + } + throw new IllegalStateException("Can not find the root project from the list of Maven modules"); + } + + public ProjectDefinition getProjectDefinition(Project project) { + for (Map.Entry<ProjectDefinition, Project> entry : projectsByDef.entrySet()) { + if (ObjectUtils.equals(entry.getValue(), project)) { + return entry.getKey(); + } + } + throw new IllegalStateException("Can not find ProjectDefinition for " + project); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/ProjectConfigurator.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/ProjectConfigurator.java new file mode 100644 index 00000000000..997cae744f2 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/ProjectConfigurator.java @@ -0,0 +1,100 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch; + +import java.util.Date; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.config.Settings; +import org.sonar.api.resources.Project; +import org.sonar.api.utils.SonarException; +import org.sonar.api.utils.System2; + +/** + * Used by views !! + * + */ +@BatchSide +public class ProjectConfigurator { + + private static final Logger LOG = LoggerFactory.getLogger(ProjectConfigurator.class); + private final System2 system2; + private Settings settings; + + public ProjectConfigurator(Settings settings, System2 system2) { + this.settings = settings; + this.system2 = system2; + } + + public Project create(ProjectDefinition definition) { + Project project = new Project(definition.getKey(), definition.getBranch(), definition.getName()); + project.setDescription(StringUtils.defaultString(definition.getDescription())); + return project; + } + + public ProjectConfigurator configure(Project project) { + Date analysisDate = loadAnalysisDate(); + project + .setAnalysisDate(analysisDate) + .setAnalysisVersion(loadAnalysisVersion()) + .setAnalysisType(loadAnalysisType()); + return this; + } + + private Date loadAnalysisDate() { + Date date; + try { + // sonar.projectDate may have been specified as a time + date = settings.getDateTime(CoreProperties.PROJECT_DATE_PROPERTY); + } catch (SonarException e) { + // this is probably just a date + date = settings.getDate(CoreProperties.PROJECT_DATE_PROPERTY); + } + if (date == null) { + date = new Date(system2.now()); + settings.setProperty(CoreProperties.PROJECT_DATE_PROPERTY, date, true); + } + return date; + } + + private Project.AnalysisType loadAnalysisType() { + String value = settings.getString(CoreProperties.DYNAMIC_ANALYSIS_PROPERTY); + if (value == null) { + return Project.AnalysisType.DYNAMIC; + } + + LOG.warn("'sonar.dynamicAnalysis' is deprecated since version 4.3 and should no longer be used."); + if ("true".equals(value)) { + return Project.AnalysisType.DYNAMIC; + } + if ("reuseReports".equals(value)) { + return Project.AnalysisType.REUSE_REPORTS; + } + return Project.AnalysisType.STATIC; + } + + private String loadAnalysisVersion() { + return settings.getString(CoreProperties.PROJECT_VERSION_PROPERTY); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/analysis/AnalysisProperties.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/analysis/AnalysisProperties.java new file mode 100644 index 00000000000..e3be61067e6 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/analysis/AnalysisProperties.java @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.analysis; + +import java.util.Map; +import javax.annotation.Nullable; +import org.sonar.batch.bootstrap.UserProperties; + +/** + * Batch properties that are specific to an analysis (for example + * coming from sonar-project.properties). + */ +public class AnalysisProperties extends UserProperties { + public AnalysisProperties(Map<String, String> properties) { + this(properties, null); + + } + + public AnalysisProperties(Map<String, String> properties, @Nullable String pathToSecretKey) { + super(properties, pathToSecretKey); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/analysis/AnalysisTempFolderProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/analysis/AnalysisTempFolderProvider.java new file mode 100644 index 00000000000..68c2608b864 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/analysis/AnalysisTempFolderProvider.java @@ -0,0 +1,80 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.analysis; + +import org.sonar.api.batch.bootstrap.ProjectReactor; +import org.picocontainer.PicoContainer; +import org.picocontainer.ComponentLifecycle; +import org.picocontainer.injectors.ProviderAdapter; +import org.sonar.api.utils.TempFolder; +import org.sonar.api.utils.internal.DefaultTempFolder; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class AnalysisTempFolderProvider extends ProviderAdapter implements ComponentLifecycle<TempFolder> { + static final String TMP_NAME = ".sonartmp"; + private DefaultTempFolder projectTempFolder; + private boolean started = false; + + public TempFolder provide(ProjectReactor projectReactor) { + if (projectTempFolder == null) { + Path workingDir = projectReactor.getRoot().getWorkDir().toPath(); + Path tempDir = workingDir.normalize().resolve(TMP_NAME); + try { + Files.deleteIfExists(tempDir); + Files.createDirectories(tempDir); + } catch (IOException e) { + throw new IllegalStateException("Unable to create root temp directory " + tempDir, e); + } + + projectTempFolder = new DefaultTempFolder(tempDir.toFile(), true); + } + return projectTempFolder; + } + + @Override + public void start(PicoContainer container) { + started = true; + } + + @Override + public void stop(PicoContainer container) { + if (projectTempFolder != null) { + projectTempFolder.stop(); + } + } + + @Override + public void dispose(PicoContainer container) { + //nothing to do + } + + @Override + public boolean componentHasLifecycle() { + return true; + } + + @Override + public boolean isStarted() { + return started; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/analysis/AnalysisWSLoaderProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/analysis/AnalysisWSLoaderProvider.java new file mode 100644 index 00000000000..54ce6e112a3 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/analysis/AnalysisWSLoaderProvider.java @@ -0,0 +1,49 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.analysis; + +import org.picocontainer.injectors.ProviderAdapter; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.batch.bootstrap.BatchWsClient; +import org.sonar.batch.cache.WSLoader; +import org.sonar.batch.cache.WSLoader.LoadStrategy; +import org.sonar.home.cache.PersistentCache; + +public class AnalysisWSLoaderProvider extends ProviderAdapter { + static final String SONAR_USE_WS_CACHE = "sonar.useWsCache"; + private WSLoader wsLoader; + + public WSLoader provide(AnalysisMode mode, PersistentCache cache, BatchWsClient client, AnalysisProperties props) { + if (wsLoader == null) { + // recreate cache directory if needed for this analysis + cache.reconfigure(); + wsLoader = new WSLoader(getStrategy(mode, props), cache, client); + } + return wsLoader; + } + + private static LoadStrategy getStrategy(AnalysisMode mode, AnalysisProperties props) { + if (mode.isIssues() && "true".equals(props.property(SONAR_USE_WS_CACHE))) { + return LoadStrategy.CACHE_ONLY; + } + + return LoadStrategy.SERVER_ONLY; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/analysis/DefaultAnalysisMode.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/analysis/DefaultAnalysisMode.java new file mode 100644 index 00000000000..bd27cf81ca0 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/analysis/DefaultAnalysisMode.java @@ -0,0 +1,121 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.analysis; + +import java.util.Map; +import javax.annotation.CheckForNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.batch.bootstrap.AbstractAnalysisMode; +import org.sonar.batch.bootstrap.GlobalProperties; +import org.sonar.batch.mediumtest.FakePluginInstaller; + +/** + * @since 4.0 + */ +public class DefaultAnalysisMode extends AbstractAnalysisMode implements AnalysisMode { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultAnalysisMode.class); + private static final String KEY_SCAN_ALL = "sonar.scanAllFiles"; + + private boolean mediumTestMode; + private boolean notAssociated; + private boolean scanAllFiles; + + public DefaultAnalysisMode(GlobalProperties globalProps, AnalysisProperties props) { + init(globalProps.properties(), props.properties()); + } + + public boolean isMediumTest() { + return mediumTestMode; + } + + public boolean isNotAssociated() { + return notAssociated; + } + + public boolean scanAllFiles() { + return scanAllFiles; + } + + private void init(Map<String, String> globalProps, Map<String, String> analysisProps) { + // make sure analysis is consistent with global properties + boolean globalPreview = isIssues(globalProps); + boolean analysisPreview = isIssues(analysisProps); + + if (!globalPreview && analysisPreview) { + throw new IllegalStateException("Inconsistent properties: global properties doesn't enable issues mode while analysis properties enables it"); + } + + load(globalProps, analysisProps); + } + + private void load(Map<String, String> globalProps, Map<String, String> analysisProps) { + String mode = getPropertyWithFallback(analysisProps, globalProps, CoreProperties.ANALYSIS_MODE); + validate(mode); + issues = CoreProperties.ANALYSIS_MODE_ISSUES.equals(mode) || CoreProperties.ANALYSIS_MODE_PREVIEW.equals(mode); + mediumTestMode = "true".equals(getPropertyWithFallback(analysisProps, globalProps, FakePluginInstaller.MEDIUM_TEST_ENABLED)); + notAssociated = issues && rootProjectKeyMissing(analysisProps); + String scanAllStr = getPropertyWithFallback(analysisProps, globalProps, KEY_SCAN_ALL); + scanAllFiles = !issues || "true".equals(scanAllStr); + } + + public void printMode() { + if (preview) { + LOG.info("Preview mode"); + } else if (issues) { + LOG.info("Issues mode"); + } else { + LOG.info("Publish mode"); + } + if (mediumTestMode) { + LOG.info("Medium test mode"); + } + if (notAssociated) { + LOG.info("Local analysis"); + } + if (!scanAllFiles) { + LOG.info("Scanning only changed files"); + } + } + + @CheckForNull + private static String getPropertyWithFallback(Map<String, String> props1, Map<String, String> props2, String key) { + if (props1.containsKey(key)) { + return props1.get(key); + } + + return props2.get(key); + } + + private static boolean isIssues(Map<String, String> props) { + String mode = props.get(CoreProperties.ANALYSIS_MODE); + + return CoreProperties.ANALYSIS_MODE_ISSUES.equals(mode); + } + + private static boolean rootProjectKeyMissing(Map<String, String> props) { + // ProjectReactorBuilder depends on this class, so it will only create this property later + return !props.containsKey(CoreProperties.PROJECT_KEY_PROPERTY); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/analysis/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/analysis/package-info.java new file mode 100644 index 00000000000..be39545404c --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/analysis/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.analysis; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/AbstractAnalysisMode.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/AbstractAnalysisMode.java new file mode 100644 index 00000000000..2944accbd27 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/AbstractAnalysisMode.java @@ -0,0 +1,67 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import org.apache.commons.lang.StringUtils; +import org.sonar.api.CoreProperties; + +import java.util.Arrays; + +import org.sonar.api.batch.AnalysisMode; + +public abstract class AbstractAnalysisMode implements AnalysisMode { + private static final String[] VALID_MODES = {CoreProperties.ANALYSIS_MODE_PREVIEW, CoreProperties.ANALYSIS_MODE_PUBLISH, CoreProperties.ANALYSIS_MODE_ISSUES}; + + protected boolean preview; + protected boolean issues; + + protected AbstractAnalysisMode() { + } + + @Override + public boolean isPreview() { + return preview; + } + + @Override + public boolean isIssues() { + return issues; + } + + @Override + public boolean isPublish() { + return !preview && !issues; + } + + protected static void validate(String mode) { + if (StringUtils.isEmpty(mode)) { + return; + } + + if (CoreProperties.ANALYSIS_MODE_INCREMENTAL.equals(mode)) { + throw new IllegalStateException("Invalid analysis mode: " + mode + ". This mode was removed in SonarQube 5.2. Valid modes are: " + Arrays.toString(VALID_MODES)); + } + + if (!Arrays.asList(VALID_MODES).contains(mode)) { + throw new IllegalStateException("Invalid analysis mode: " + mode + ". Valid modes are: " + Arrays.toString(VALID_MODES)); + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java new file mode 100644 index 00000000000..0a2ec661d73 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java @@ -0,0 +1,90 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import com.google.common.collect.Lists; +import java.util.Collection; +import java.util.List; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.batch.cpd.CpdComponents; +import org.sonar.batch.issue.tracking.ServerIssueFromWs; +import org.sonar.batch.issue.tracking.TrackedIssue; +import org.sonar.batch.scan.report.ConsoleReport; +import org.sonar.batch.scan.report.HtmlReport; +import org.sonar.batch.scan.report.IssuesReportBuilder; +import org.sonar.batch.scan.report.JSONReport; +import org.sonar.batch.scan.report.RuleNameProvider; +import org.sonar.batch.scan.report.SourceProvider; +import org.sonar.batch.scm.ScmConfiguration; +import org.sonar.batch.scm.ScmSensor; +import org.sonar.batch.source.CodeColorizerSensor; +import org.sonar.batch.source.LinesSensor; +import org.sonar.batch.source.ZeroCoverageSensor; +import org.sonar.batch.task.ListTask; +import org.sonar.batch.task.ScanTask; +import org.sonar.batch.task.Tasks; +import org.sonar.core.component.DefaultResourceTypes; +import org.sonar.core.config.CorePropertyDefinitions; +import org.sonar.core.issue.tracking.Tracker; +import org.sonar.core.platform.SonarQubeVersionProvider; + +public class BatchComponents { + private BatchComponents() { + // only static stuff + } + + public static Collection<Object> all(AnalysisMode analysisMode) { + List<Object> components = Lists.newArrayList( + new SonarQubeVersionProvider(), + DefaultResourceTypes.get(), + + // Tasks + Tasks.class, + ListTask.DEFINITION, + ListTask.class, + ScanTask.DEFINITION, + ScanTask.class); + components.addAll(CorePropertyDefinitions.all()); + if (!analysisMode.isIssues()) { + // SCM + components.add(ScmConfiguration.class); + components.add(ScmSensor.class); + + components.add(LinesSensor.class); + components.add(ZeroCoverageSensor.class); + components.add(CodeColorizerSensor.class); + + // CPD + components.addAll(CpdComponents.all()); + } else { + // Issues tracking + components.add(new Tracker<TrackedIssue, ServerIssueFromWs>()); + + // Issues report + components.add(ConsoleReport.class); + components.add(JSONReport.class); + components.add(HtmlReport.class); + components.add(IssuesReportBuilder.class); + components.add(SourceProvider.class); + components.add(RuleNameProvider.class); + } + return components; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java new file mode 100644 index 00000000000..cfebad6cbb5 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java @@ -0,0 +1,264 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import com.google.common.base.Predicates; +import com.google.common.collect.Collections2; +import com.google.common.collect.Lists; +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 javax.annotation.Nullable; +import org.apache.commons.lang.ClassUtils; +import org.sonar.api.batch.CheckProject; +import org.sonar.api.batch.DependedUpon; +import org.sonar.api.batch.DependsUpon; +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.sensor.Sensor; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.resources.Project; +import org.sonar.api.utils.AnnotationUtils; +import org.sonar.api.utils.dag.DirectAcyclicGraph; +import org.sonar.batch.postjob.PostJobOptimizer; +import org.sonar.batch.postjob.PostJobWrapper; +import org.sonar.batch.sensor.DefaultSensorContext; +import org.sonar.batch.sensor.SensorOptimizer; +import org.sonar.batch.sensor.SensorWrapper; +import org.sonar.core.platform.ComponentContainer; + +/** + * @since 2.6 + */ +public class BatchExtensionDictionnary { + + private final ComponentContainer componentContainer; + private final SensorContext sensorContext; + private final SensorOptimizer sensorOptimizer; + private final PostJobContext postJobContext; + private final PostJobOptimizer postJobOptimizer; + + public BatchExtensionDictionnary(ComponentContainer componentContainer, DefaultSensorContext sensorContext, SensorOptimizer sensorOptimizer, PostJobContext postJobContext, + PostJobOptimizer postJobOptimizer) { + this.componentContainer = componentContainer; + this.sensorContext = sensorContext; + this.sensorOptimizer = sensorOptimizer; + this.postJobContext = postJobContext; + this.postJobOptimizer = postJobOptimizer; + } + + public <T> Collection<T> select(Class<T> type, @Nullable Project project, boolean sort, @Nullable ExtensionMatcher matcher) { + List<T> result = getFilteredExtensions(type, project, matcher); + if (sort) { + return sort(result); + } + return result; + } + + private static Phase.Name evaluatePhase(Object extension) { + Object extensionToEvaluate; + if (extension instanceof SensorWrapper) { + extensionToEvaluate = ((SensorWrapper) extension).wrappedSensor(); + } else { + extensionToEvaluate = extension; + } + Phase phaseAnnotation = AnnotationUtils.getAnnotation(extensionToEvaluate, Phase.class); + if (phaseAnnotation != null) { + return phaseAnnotation.name(); + } + return Phase.Name.DEFAULT; + } + + private <T> List<T> getFilteredExtensions(Class<T> type, @Nullable Project project, @Nullable ExtensionMatcher matcher) { + List<T> result = Lists.newArrayList(); + for (Object extension : getExtensions(type)) { + if (org.sonar.api.batch.Sensor.class.equals(type) && extension instanceof Sensor) { + extension = new SensorWrapper((Sensor) extension, sensorContext, sensorOptimizer); + } + if (shouldKeep(type, extension, project, matcher)) { + result.add((T) extension); + } + } + if (org.sonar.api.batch.Sensor.class.equals(type)) { + // Retrieve new Sensors and wrap then in SensorWrapper + for (Object extension : getExtensions(Sensor.class)) { + extension = new SensorWrapper((Sensor) extension, sensorContext, sensorOptimizer); + if (shouldKeep(type, extension, project, matcher)) { + result.add((T) extension); + } + } + } + if (org.sonar.api.batch.PostJob.class.equals(type)) { + // Retrieve new PostJob and wrap then in PostJobWrapper + for (Object extension : getExtensions(PostJob.class)) { + extension = new PostJobWrapper((PostJob) extension, postJobContext, postJobOptimizer); + if (shouldKeep(type, extension, project, matcher)) { + result.add((T) extension); + } + } + } + return result; + } + + protected List<Object> getExtensions(Class type) { + List<Object> extensions = Lists.newArrayList(); + completeBatchExtensions(componentContainer, extensions, type); + return extensions; + } + + private static void completeBatchExtensions(ComponentContainer container, List<Object> extensions, Class type) { + if (container != null) { + extensions.addAll(container.getComponentsByType(type)); + completeBatchExtensions(container.getParent(), extensions, type); + } + } + + public <T> Collection<T> sort(Collection<T> 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 Collections2.filter(sortedList, Predicates.in(extensions)); + } + + /** + * Extension dependencies + */ + private <T> List<Object> getDependencies(T extension) { + List<Object> result = new ArrayList<>(); + result.addAll(evaluateAnnotatedClasses(extension, DependsUpon.class)); + return result; + } + + /** + * Objects that depend upon this extension. + */ + public <T> List<Object> getDependents(T extension) { + List<Object> result = new ArrayList<>(); + result.addAll(evaluateAnnotatedClasses(extension, DependedUpon.class)); + return result; + } + + 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); + } + } + } + + protected List<Object> evaluateAnnotatedClasses(Object extension, Class<? extends Annotation> annotation) { + List<Object> results = Lists.newArrayList(); + 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<Object> 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<Object> 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 Project project, @Nullable ExtensionMatcher matcher) { + boolean keep = (ClassUtils.isAssignable(extension.getClass(), type) + || (org.sonar.api.batch.Sensor.class.equals(type) && ClassUtils.isAssignable(extension.getClass(), Sensor.class))) + && (matcher == null || matcher.accept(extension)); + if (keep && project != null && ClassUtils.isAssignable(extension.getClass(), CheckProject.class)) { + keep = ((CheckProject) extension).shouldExecuteOnProject(project); + } + return keep; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java new file mode 100644 index 00000000000..1c1ee8e1fc4 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java @@ -0,0 +1,159 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Lists; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.CharUtils; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.SonarPlugin; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.api.utils.log.Profiler; +import org.sonar.batch.cache.WSLoader; +import org.sonar.batch.cache.WSLoaderResult; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.RemotePlugin; +import org.sonar.core.platform.RemotePluginFile; +import org.sonar.home.cache.FileCache; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.WsResponse; + +import static java.lang.String.format; + +/** + * Downloads the plugins installed on server and stores them in a local user cache + * (see {@link FileCacheProvider}). + */ +public class BatchPluginInstaller implements PluginInstaller { + + private static final Logger LOG = Loggers.get(BatchPluginInstaller.class); + private static final String PLUGINS_INDEX_URL = "/deploy/plugins/index.txt"; + + private final WSLoader wsLoader; + private final FileCache fileCache; + private final BatchPluginPredicate pluginPredicate; + private final BatchWsClient wsClient; + + public BatchPluginInstaller(WSLoader wsLoader, BatchWsClient wsClient, FileCache fileCache, BatchPluginPredicate pluginPredicate) { + this.wsLoader = wsLoader; + this.fileCache = fileCache; + this.pluginPredicate = pluginPredicate; + this.wsClient = wsClient; + } + + @Override + public Map<String, PluginInfo> installRemotes() { + return loadPlugins(listRemotePlugins()); + } + + private Map<String, PluginInfo> loadPlugins(List<RemotePlugin> remotePlugins) { + Map<String, PluginInfo> infosByKey = new HashMap<>(); + + Profiler profiler = Profiler.create(LOG).startDebug("Load plugins"); + + for (RemotePlugin remotePlugin : remotePlugins) { + if (pluginPredicate.apply(remotePlugin.getKey())) { + File jarFile = download(remotePlugin); + PluginInfo info = PluginInfo.create(jarFile); + infosByKey.put(info.getKey(), info); + } + } + + profiler.stopDebug(); + return infosByKey; + } + + /** + * Returns empty on purpose. This method is used only by tests. + * @see org.sonar.batch.mediumtest.BatchMediumTester + */ + @Override + public Map<String, SonarPlugin> installLocals() { + return Collections.emptyMap(); + } + + @VisibleForTesting + File download(final RemotePlugin remote) { + try { + final RemotePluginFile file = remote.file(); + return fileCache.get(file.getFilename(), file.getHash(), new FileDownloader(remote.getKey())); + } catch (Exception e) { + throw new IllegalStateException("Fail to download plugin: " + remote.getKey(), e); + } + } + + /** + * Gets information about the plugins installed on server (filename, checksum) + */ + @VisibleForTesting + List<RemotePlugin> listRemotePlugins() { + try { + String pluginIndex = loadPluginIndex(); + String[] rows = StringUtils.split(pluginIndex, CharUtils.LF); + List<RemotePlugin> result = Lists.newArrayList(); + for (String row : rows) { + result.add(RemotePlugin.unmarshal(row)); + } + return result; + + } catch (Exception e) { + throw new IllegalStateException("Fail to load plugin index: " + PLUGINS_INDEX_URL, e); + } + } + + private String loadPluginIndex() { + Profiler profiler = Profiler.create(LOG).startInfo("Load plugins index"); + WSLoaderResult<String> wsResult = wsLoader.loadString(PLUGINS_INDEX_URL); + profiler.stopInfo(wsResult.isFromCache()); + return wsResult.get(); + } + + private class FileDownloader implements FileCache.Downloader { + private String key; + + FileDownloader(String key) { + this.key = key; + } + + @Override + public void download(String filename, File toFile) throws IOException { + String url = format("/deploy/plugins/%s/%s", key, filename); + if (LOG.isDebugEnabled()) { + LOG.debug("Download plugin {} to {}", filename, toFile); + } else { + LOG.info("Download {}", filename); + } + + WsResponse response = wsClient.call(new GetRequest(url)); + try (InputStream stream = response.contentStream()) { + FileUtils.copyInputStreamToFile(stream, toFile); + } + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchPluginJarExploder.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchPluginJarExploder.java new file mode 100644 index 00000000000..a91a6b38cbc --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchPluginJarExploder.java @@ -0,0 +1,80 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import org.apache.commons.io.FileUtils; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.utils.ZipUtils; +import org.sonar.core.platform.ExplodedPlugin; +import org.sonar.core.platform.PluginJarExploder; +import org.sonar.core.platform.PluginInfo; +import org.sonar.home.cache.FileCache; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +import static org.sonar.core.util.FileUtils.deleteQuietly; + +@BatchSide +public class BatchPluginJarExploder extends PluginJarExploder { + + private final FileCache fileCache; + + public BatchPluginJarExploder(FileCache fileCache) { + this.fileCache = fileCache; + } + + @Override + public ExplodedPlugin explode(PluginInfo info) { + try { + File dir = unzipFile(info.getNonNullJarFile()); + return explodeFromUnzippedDir(info.getKey(), info.getNonNullJarFile(), dir); + } catch (Exception e) { + throw new IllegalStateException(String.format("Fail to open plugin [%s]: %s", info.getKey(), info.getNonNullJarFile().getAbsolutePath()), e); + } + } + + private File unzipFile(File cachedFile) throws IOException { + String filename = cachedFile.getName(); + File destDir = new File(cachedFile.getParentFile(), filename + "_unzip"); + File lockFile = new File(cachedFile.getParentFile(), filename + "_unzip.lock"); + if (!destDir.exists()) { + FileOutputStream out = new FileOutputStream(lockFile); + try { + java.nio.channels.FileLock lock = out.getChannel().lock(); + try { + // Recheck in case of concurrent processes + if (!destDir.exists()) { + File tempDir = fileCache.createTempDir(); + ZipUtils.unzip(cachedFile, tempDir, newLibFilter()); + FileUtils.moveDirectory(tempDir, destDir); + } + } finally { + lock.release(); + } + } finally { + out.close(); + deleteQuietly(lockFile); + } + } + return destDir; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchPluginPredicate.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchPluginPredicate.java new file mode 100644 index 00000000000..ea4380c44da --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchPluginPredicate.java @@ -0,0 +1,98 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import com.google.common.base.Joiner; +import com.google.common.base.Predicate; +import com.google.common.base.Splitter; +import com.google.common.collect.Lists; + +import java.util.List; +import java.util.Set; + +import javax.annotation.Nonnull; + +import org.apache.commons.lang.StringUtils; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.config.Settings; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import static com.google.common.collect.Sets.newHashSet; + +/** + * Filters the plugins to be enabled during analysis + */ +@BatchSide +public class BatchPluginPredicate implements Predicate<String> { + + private static final Logger LOG = Loggers.get(BatchPluginPredicate.class); + + private static final String BUILDBREAKER_PLUGIN_KEY = "buildbreaker"; + private static final Joiner COMMA_JOINER = Joiner.on(", "); + + private final Set<String> whites = newHashSet(); + private final Set<String> blacks = newHashSet(); + private final GlobalMode mode; + + public BatchPluginPredicate(Settings settings, GlobalMode mode) { + this.mode = mode; + if (mode.isPreview() || mode.isIssues()) { + // These default values are not supported by Settings because the class CorePlugin + // is not loaded yet. + whites.addAll(propertyValues(settings, + CoreProperties.PREVIEW_INCLUDE_PLUGINS, CoreProperties.PREVIEW_INCLUDE_PLUGINS_DEFAULT_VALUE)); + blacks.addAll(propertyValues(settings, + CoreProperties.PREVIEW_EXCLUDE_PLUGINS, CoreProperties.PREVIEW_EXCLUDE_PLUGINS_DEFAULT_VALUE)); + } + if (!whites.isEmpty()) { + LOG.info("Include plugins: " + COMMA_JOINER.join(whites)); + } + if (!blacks.isEmpty()) { + LOG.info("Exclude plugins: " + COMMA_JOINER.join(blacks)); + } + } + + @Override + public boolean apply(@Nonnull String pluginKey) { + if (BUILDBREAKER_PLUGIN_KEY.equals(pluginKey) && mode.isPreview()) { + LOG.info("Build Breaker plugin is no more supported in preview mode"); + return false; + } + + if (whites.isEmpty()) { + return blacks.isEmpty() || !blacks.contains(pluginKey); + } + return whites.contains(pluginKey); + } + + Set<String> getWhites() { + return whites; + } + + Set<String> getBlacks() { + return blacks; + } + + private static List<String> propertyValues(Settings settings, String key, String defaultValue) { + String s = StringUtils.defaultIfEmpty(settings.getString(key), defaultValue); + return Lists.newArrayList(Splitter.on(",").trimResults().omitEmptyStrings().split(s)); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java new file mode 100644 index 00000000000..4d0d0c46c2d --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java @@ -0,0 +1,110 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import java.util.Collection; +import java.util.Map; +import org.picocontainer.Startable; +import org.sonar.api.SonarPlugin; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginLoader; +import org.sonar.core.platform.PluginRepository; + +/** + * Orchestrates the installation and loading of plugins + */ +public class BatchPluginRepository implements PluginRepository, Startable { + private static final Logger LOG = Loggers.get(BatchPluginRepository.class); + + private final PluginInstaller installer; + private final PluginLoader loader; + + private Map<String, SonarPlugin> pluginInstancesByKeys; + private Map<String, PluginInfo> infosByKeys; + + public BatchPluginRepository(PluginInstaller installer, PluginLoader loader) { + this.installer = installer; + this.loader = loader; + } + + @Override + public void start() { + infosByKeys = Maps.newHashMap(installer.installRemotes()); + pluginInstancesByKeys = Maps.newHashMap(loader.load(infosByKeys)); + + // this part is only used by tests + for (Map.Entry<String, SonarPlugin> entry : installer.installLocals().entrySet()) { + String pluginKey = entry.getKey(); + infosByKeys.put(pluginKey, new PluginInfo(pluginKey)); + pluginInstancesByKeys.put(pluginKey, entry.getValue()); + } + + logPlugins(); + } + + private void logPlugins() { + if (infosByKeys.isEmpty()) { + LOG.debug("No plugins loaded"); + } else { + LOG.debug("Plugins:"); + for (PluginInfo p : infosByKeys.values()) { + LOG.debug(" * {} {} ({})", p.getName(), p.getVersion(), p.getKey()); + } + } + } + + @Override + public void stop() { + // close plugin classloaders + loader.unload(pluginInstancesByKeys.values()); + + pluginInstancesByKeys.clear(); + infosByKeys.clear(); + } + + @Override + public Collection<PluginInfo> getPluginInfos() { + return infosByKeys.values(); + } + + @Override + public PluginInfo getPluginInfo(String key) { + PluginInfo info = infosByKeys.get(key); + Preconditions.checkState(info != null, String.format("Plugin [%s] does not exist", key)); + return info; + } + + @Override + public SonarPlugin getPluginInstance(String key) { + SonarPlugin instance = pluginInstancesByKeys.get(key); + Preconditions.checkState(instance != null, String.format("Plugin [%s] does not exist", key)); + return instance; + } + + @Override + public boolean hasPlugin(String key) { + return infosByKeys.containsKey(key); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchWsClient.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchWsClient.java new file mode 100644 index 00000000000..d77670a6c86 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchWsClient.java @@ -0,0 +1,113 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Joiner; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import java.net.HttpURLConnection; +import java.util.ArrayList; +import java.util.List; +import org.sonar.api.CoreProperties; +import org.sonar.api.utils.MessageException; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.api.utils.log.Profiler; +import org.sonarqube.ws.client.HttpException; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.WsConnector; +import org.sonarqube.ws.client.WsRequest; +import org.sonarqube.ws.client.WsResponse; + +import static java.lang.String.format; + +public class BatchWsClient { + + private static final Logger LOG = Loggers.get(BatchWsClient.class); + + private final WsClient target; + private final boolean hasCredentials; + + public BatchWsClient(WsClient target, boolean hasCredentials) { + this.target = target; + this.hasCredentials = hasCredentials; + } + + /** + * @throws IllegalStateException if the request could not be executed due to + * a connectivity problem or timeout. Because networks can + * fail during an exchange, it is possible that the remote server + * accepted the request before the failure + * @throws HttpException if the response code is not in range [200..300) + */ + public WsResponse call(WsRequest request) { + Profiler profiler = Profiler.createIfDebug(LOG).start(); + WsResponse response = target.wsConnector().call(request); + profiler.stopDebug(format("%s %d %s", request.getMethod(), response.code(), response.requestUrl())); + failIfUnauthorized(response); + return response; + } + + public String baseUrl() { + return target.wsConnector().baseUrl(); + } + + @VisibleForTesting + WsConnector wsConnector() { + return target.wsConnector(); + } + + private void failIfUnauthorized(WsResponse response) { + if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) { + if (hasCredentials) { + // credentials are not valid + throw MessageException.of(format("Not authorized. Please check the properties %s and %s.", + CoreProperties.LOGIN, CoreProperties.PASSWORD)); + } + // not authenticated - see https://jira.sonarsource.com/browse/SONAR-4048 + throw MessageException.of(format("Not authorized. Analyzing this project requires to be authenticated. " + + "Please provide the values of the properties %s and %s.", CoreProperties.LOGIN, CoreProperties.PASSWORD)); + + } + if (response.code() == HttpURLConnection.HTTP_FORBIDDEN) { + // SONAR-4397 Details are in response content + throw MessageException.of(tryParseAsJsonError(response.content())); + } + response.failIfNotSuccessful(); + } + + private static String tryParseAsJsonError(String responseContent) { + try { + JsonParser parser = new JsonParser(); + JsonObject obj = parser.parse(responseContent).getAsJsonObject(); + JsonArray errors = obj.getAsJsonArray("errors"); + List<String> errorMessages = new ArrayList<>(); + for (JsonElement e : errors) { + errorMessages.add(e.getAsJsonObject().get("msg").getAsString()); + } + return Joiner.on(", ").join(errorMessages); + } catch (Exception e) { + return responseContent; + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchWsClientProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchWsClientProvider.java new file mode 100644 index 00000000000..d33baf821e5 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchWsClientProvider.java @@ -0,0 +1,62 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import org.picocontainer.injectors.ProviderAdapter; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.BatchSide; +import org.sonar.batch.bootstrapper.EnvironmentInformation; +import org.sonarqube.ws.client.HttpConnector; +import org.sonarqube.ws.client.HttpWsClient; + +import static java.lang.Integer.parseInt; +import static java.lang.String.valueOf; +import static org.apache.commons.lang.StringUtils.defaultIfBlank; + +@BatchSide +public class BatchWsClientProvider extends ProviderAdapter { + + static final int CONNECT_TIMEOUT_MS = 5_000; + static final String READ_TIMEOUT_SEC_PROPERTY = "sonar.ws.timeout"; + static final int DEFAULT_READ_TIMEOUT_SEC = 60; + + private BatchWsClient wsClient; + + public synchronized BatchWsClient provide(final GlobalProperties settings, final EnvironmentInformation env) { + if (wsClient == null) { + String url = defaultIfBlank(settings.property("sonar.host.url"), CoreProperties.SERVER_BASE_URL_DEFAULT_VALUE); + HttpConnector.Builder builder = new HttpConnector.Builder(); + + // TODO proxy + + String timeoutSec = defaultIfBlank(settings.property(READ_TIMEOUT_SEC_PROPERTY), valueOf(DEFAULT_READ_TIMEOUT_SEC)); + String login = defaultIfBlank(settings.property(CoreProperties.LOGIN), null); + builder + .readTimeoutMilliseconds(parseInt(timeoutSec) * 1_000) + .connectTimeoutMilliseconds(CONNECT_TIMEOUT_MS) + .userAgent(env.toString()) + .url(url) + .credentials(login, settings.property(CoreProperties.PASSWORD)); + + wsClient = new BatchWsClient(new HttpWsClient(builder.build()), login != null); + } + return wsClient; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/DroppedPropertyChecker.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/DroppedPropertyChecker.java new file mode 100644 index 00000000000..72b34d613ea --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/DroppedPropertyChecker.java @@ -0,0 +1,48 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import java.util.Map; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; + +import static java.util.Objects.requireNonNull; + +public class DroppedPropertyChecker { + + private static final Logger LOG = Loggers.get(DroppedPropertyChecker.class); + + private final Map<String, String> settings; + private final Map<String, String> properties; + + public DroppedPropertyChecker(Map<String, String> properties, Map<String, String> droppedPropertiesAndMsg) { + this.settings = requireNonNull(properties); + this.properties = requireNonNull(droppedPropertiesAndMsg); + } + + public void checkDroppedProperties() { + for (Map.Entry<String, String> entry : properties.entrySet()) { + if (settings.containsKey(entry.getKey())) { + LOG.warn("Property '{}' is not supported any more. {}", entry.getKey(), entry.getValue()); + } + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java new file mode 100644 index 00000000000..ff610139032 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java @@ -0,0 +1,77 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import java.util.List; +import javax.annotation.Nullable; +import org.sonar.api.ExtensionProvider; +import org.sonar.api.SonarPlugin; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.core.platform.ComponentContainer; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginRepository; + +public class ExtensionInstaller { + + private final PluginRepository pluginRepository; + private final AnalysisMode analysisMode; + + public ExtensionInstaller(PluginRepository pluginRepository, AnalysisMode analysisMode) { + this.pluginRepository = pluginRepository; + this.analysisMode = analysisMode; + } + + public ExtensionInstaller install(ComponentContainer container, ExtensionMatcher matcher) { + + // core components + for (Object o : BatchComponents.all(analysisMode)) { + doInstall(container, matcher, null, o); + } + + // plugin extensions + for (PluginInfo pluginInfo : pluginRepository.getPluginInfos()) { + SonarPlugin plugin = pluginRepository.getPluginInstance(pluginInfo.getKey()); + for (Object extension : plugin.getExtensions()) { + doInstall(container, matcher, pluginInfo, extension); + } + } + List<ExtensionProvider> providers = container.getComponentsByType(ExtensionProvider.class); + for (ExtensionProvider provider : providers) { + Object object = provider.provide(); + if (object instanceof Iterable) { + for (Object extension : (Iterable) object) { + doInstall(container, matcher, null, extension); + } + } else { + doInstall(container, matcher, null, object); + } + } + return this; + } + + private static void doInstall(ComponentContainer container, ExtensionMatcher matcher, @Nullable PluginInfo pluginInfo, Object extension) { + if (matcher.accept(extension)) { + container.addExtension(pluginInfo, extension); + } else { + container.declareExtension(pluginInfo, extension); + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/ExtensionMatcher.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/ExtensionMatcher.java new file mode 100644 index 00000000000..d38581f572d --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/ExtensionMatcher.java @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import org.sonar.api.batch.BatchSide; + +/** + * @since 3.6 + */ +@BatchSide +public interface ExtensionMatcher { + boolean accept(Object extension); +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/ExtensionUtils.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/ExtensionUtils.java new file mode 100644 index 00000000000..adf16a40315 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/ExtensionUtils.java @@ -0,0 +1,48 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.InstantiationStrategy; +import org.sonar.api.utils.AnnotationUtils; + +public class ExtensionUtils { + + private ExtensionUtils() { + // only static methods + } + + public static boolean isInstantiationStrategy(Object extension, String strategy) { + InstantiationStrategy annotation = AnnotationUtils.getAnnotation(extension, InstantiationStrategy.class); + if (annotation != null) { + return strategy.equals(annotation.value()); + } + return InstantiationStrategy.PER_PROJECT.equals(strategy); + } + + public static boolean isBatchSide(Object extension) { + return AnnotationUtils.getAnnotation(extension, BatchSide.class) != null; + } + + public static boolean isType(Object extension, Class<?> extensionClass) { + Class clazz = extension instanceof Class ? (Class) extension : extension.getClass(); + return extensionClass.isAssignableFrom(clazz); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/FileCacheProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/FileCacheProvider.java new file mode 100644 index 00000000000..9880c7de23b --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/FileCacheProvider.java @@ -0,0 +1,37 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import org.picocontainer.injectors.ProviderAdapter; +import org.sonar.api.config.Settings; +import org.sonar.home.cache.FileCache; +import org.sonar.home.cache.FileCacheBuilder; + +public class FileCacheProvider extends ProviderAdapter { + private FileCache cache; + + public FileCache provide(Settings settings) { + if (cache == null) { + String home = settings.getString("sonar.userHome"); + cache = new FileCacheBuilder(new Slf4jLogger()).setUserHome(home).build(); + } + return cache; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java new file mode 100644 index 00000000000..8ac69fd6258 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java @@ -0,0 +1,129 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import java.util.List; +import java.util.Map; +import org.sonar.api.SonarPlugin; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.UriReader; +import org.sonar.batch.cache.GlobalPersistentCacheProvider; +import org.sonar.batch.cache.ProjectSyncContainer; +import org.sonar.batch.cache.StrategyWSLoaderProvider; +import org.sonar.batch.cache.WSLoader.LoadStrategy; +import org.sonar.batch.index.CachesManager; +import org.sonar.batch.platform.DefaultServer; +import org.sonar.batch.repository.DefaultGlobalRepositoriesLoader; +import org.sonar.batch.repository.GlobalRepositoriesLoader; +import org.sonar.batch.repository.GlobalRepositoriesProvider; +import org.sonar.batch.task.TaskContainer; +import org.sonar.core.platform.ComponentContainer; +import org.sonar.core.platform.PluginClassloaderFactory; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginLoader; +import org.sonar.core.platform.PluginRepository; +import org.sonar.core.util.DefaultHttpDownloader; +import org.sonar.core.util.UuidFactoryImpl; + +public class GlobalContainer extends ComponentContainer { + + private final Map<String, String> bootstrapProperties; + private boolean preferCache; + + private GlobalContainer(Map<String, String> bootstrapProperties, boolean preferCache) { + super(); + this.bootstrapProperties = bootstrapProperties; + this.preferCache = preferCache; + } + + public static GlobalContainer create(Map<String, String> bootstrapProperties, List<?> extensions, boolean preferCache) { + GlobalContainer container = new GlobalContainer(bootstrapProperties, preferCache); + container.add(extensions); + return container; + } + + @Override + protected void doBeforeStart() { + GlobalProperties bootstrapProps = new GlobalProperties(bootstrapProperties); + GlobalMode globalMode = new GlobalMode(bootstrapProps); + LoadStrategy strategy = getDataLoadingStrategy(globalMode, preferCache); + StrategyWSLoaderProvider wsLoaderProvider = new StrategyWSLoaderProvider(strategy); + add(wsLoaderProvider); + add(bootstrapProps); + add(globalMode); + addBootstrapComponents(); + } + + private static LoadStrategy getDataLoadingStrategy(GlobalMode mode, boolean preferCache) { + if (!mode.isIssues()) { + return LoadStrategy.SERVER_ONLY; + } + + return preferCache ? LoadStrategy.CACHE_FIRST : LoadStrategy.SERVER_FIRST; + } + + private void addBootstrapComponents() { + add( + // plugins + BatchPluginRepository.class, + PluginLoader.class, + PluginClassloaderFactory.class, + BatchPluginJarExploder.class, + BatchPluginPredicate.class, + ExtensionInstaller.class, + + CachesManager.class, + GlobalSettings.class, + new BatchWsClientProvider(), + DefaultServer.class, + new GlobalTempFolderProvider(), + DefaultHttpDownloader.class, + UriReader.class, + new FileCacheProvider(), + new GlobalPersistentCacheProvider(), + System2.INSTANCE, + new GlobalRepositoriesProvider(), + UuidFactoryImpl.INSTANCE); + addIfMissing(BatchPluginInstaller.class, PluginInstaller.class); + addIfMissing(DefaultGlobalRepositoriesLoader.class, GlobalRepositoriesLoader.class); + } + + @Override + protected void doAfterStart() { + installPlugins(); + } + + private void installPlugins() { + PluginRepository pluginRepository = getComponentByType(PluginRepository.class); + for (PluginInfo pluginInfo : pluginRepository.getPluginInfos()) { + SonarPlugin instance = pluginRepository.getPluginInstance(pluginInfo.getKey()); + addExtension(pluginInfo, instance); + } + } + + public void executeTask(Map<String, String> taskProperties, Object... components) { + new TaskContainer(this, taskProperties, components).execute(); + } + + public void syncProject(String projectKey, boolean force) { + new ProjectSyncContainer(this, projectKey, force).execute(); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/GlobalMode.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/GlobalMode.java new file mode 100644 index 00000000000..774dac71361 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/GlobalMode.java @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import org.sonar.api.CoreProperties; + +import org.sonar.api.batch.AnalysisMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class GlobalMode extends AbstractAnalysisMode implements AnalysisMode { + private static final Logger LOG = LoggerFactory.getLogger(GlobalMode.class); + + public GlobalMode(GlobalProperties props) { + String mode = props.property(CoreProperties.ANALYSIS_MODE); + validate(mode); + issues = CoreProperties.ANALYSIS_MODE_ISSUES.equals(mode) || CoreProperties.ANALYSIS_MODE_PREVIEW.equals(mode); + + if (preview) { + LOG.debug("Preview global mode"); + } else if (issues) { + LOG.debug("Issues global mode"); + } else { + LOG.debug("Publish global mode"); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/GlobalProperties.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/GlobalProperties.java new file mode 100644 index 00000000000..62bc070080e --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/GlobalProperties.java @@ -0,0 +1,36 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import org.sonar.api.CoreProperties; + +import java.util.Map; + +/** + * Immutable batch properties that are not specific to a task (for example + * coming from global configuration file of sonar-runner). + */ +public class GlobalProperties extends UserProperties { + + public GlobalProperties(Map<String, String> properties) { + super(properties, properties.get(CoreProperties.ENCRYPTION_SECRET_KEY_PATH)); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/GlobalSettings.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/GlobalSettings.java new file mode 100644 index 00000000000..3ff5dce8ecd --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/GlobalSettings.java @@ -0,0 +1,78 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.CoreProperties; +import org.sonar.api.config.PropertyDefinitions; +import org.sonar.api.config.Settings; +import org.sonar.api.utils.MessageException; +import org.sonar.scanner.protocol.input.GlobalRepositories; + +public class GlobalSettings extends Settings { + + private static final Logger LOG = LoggerFactory.getLogger(GlobalSettings.class); + + private static final String JDBC_SPECIFIC_MESSAGE = "It will be ignored. There is no longer any DB connection to the SQ database."; + /** + * A map of dropped properties as key and specific message to display for that property + * (what will happen, what should the user do, ...) as a value + */ + private static final Map<String, String> DROPPED_PROPERTIES = ImmutableMap.of( + "sonar.jdbc.url", JDBC_SPECIFIC_MESSAGE, + "sonar.jdbc.username", JDBC_SPECIFIC_MESSAGE, + "sonar.jdbc.password", JDBC_SPECIFIC_MESSAGE); + + private final GlobalProperties bootstrapProps; + private final GlobalRepositories globalReferentials; + private final GlobalMode mode; + + public GlobalSettings(GlobalProperties bootstrapProps, PropertyDefinitions propertyDefinitions, + GlobalRepositories globalReferentials, GlobalMode mode) { + + super(propertyDefinitions); + this.mode = mode; + getEncryption().setPathToSecretKey(bootstrapProps.property(CoreProperties.ENCRYPTION_SECRET_KEY_PATH)); + this.bootstrapProps = bootstrapProps; + this.globalReferentials = globalReferentials; + init(); + new DroppedPropertyChecker(this.getProperties(), DROPPED_PROPERTIES).checkDroppedProperties(); + } + + private void init() { + addProperties(globalReferentials.globalSettings()); + addProperties(bootstrapProps.properties()); + + if (hasKey(CoreProperties.PERMANENT_SERVER_ID)) { + LOG.info("Server id: " + getString(CoreProperties.PERMANENT_SERVER_ID)); + } + } + + @Override + protected void doOnGetProperties(String key) { + if (mode.isIssues() && key.endsWith(".secured") && !key.contains(".license")) { + throw MessageException.of("Access to the secured property '" + key + + "' is not possible in issues mode. The SonarQube plugin which requires this property must be deactivated in issues mode."); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/GlobalTempFolderProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/GlobalTempFolderProvider.java new file mode 100644 index 00000000000..2ea3bff55a3 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/GlobalTempFolderProvider.java @@ -0,0 +1,175 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.concurrent.TimeUnit; +import org.apache.commons.lang.StringUtils; +import org.picocontainer.ComponentLifecycle; +import org.picocontainer.PicoContainer; +import org.picocontainer.injectors.ProviderAdapter; +import org.sonar.api.CoreProperties; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.TempFolder; +import org.sonar.api.utils.internal.DefaultTempFolder; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; + +import static org.sonar.core.util.FileUtils.deleteQuietly; + +public class GlobalTempFolderProvider extends ProviderAdapter implements ComponentLifecycle<TempFolder> { + private static final Logger LOG = Loggers.get(GlobalTempFolderProvider.class); + private static final long CLEAN_MAX_AGE = TimeUnit.DAYS.toMillis(21); + static final String TMP_NAME_PREFIX = ".sonartmp_"; + private boolean started = false; + + private System2 system; + private DefaultTempFolder tempFolder; + + public GlobalTempFolderProvider() { + this(new System2()); + } + + GlobalTempFolderProvider(System2 system) { + this.system = system; + } + + public TempFolder provide(GlobalProperties bootstrapProps) { + if (tempFolder == null) { + + String workingPathName = StringUtils.defaultIfBlank(bootstrapProps.property(CoreProperties.GLOBAL_WORKING_DIRECTORY), CoreProperties.GLOBAL_WORKING_DIRECTORY_DEFAULT_VALUE); + Path workingPath = Paths.get(workingPathName); + + if (!workingPath.isAbsolute()) { + Path home = findSonarHome(bootstrapProps); + workingPath = home.resolve(workingPath).normalize(); + } + + try { + cleanTempFolders(workingPath); + } catch (IOException e) { + LOG.error(String.format("failed to clean global working directory: %s", workingPath), e); + } + Path tempDir = createTempFolder(workingPath); + tempFolder = new DefaultTempFolder(tempDir.toFile(), true); + } + return tempFolder; + } + + private static Path createTempFolder(Path workingPath) { + try { + Files.createDirectories(workingPath); + } catch (IOException e) { + throw new IllegalStateException("Failed to create working path: " + workingPath, e); + } + + try { + return Files.createTempDirectory(workingPath, TMP_NAME_PREFIX); + } catch (IOException e) { + throw new IllegalStateException("Failed to create temporary folder in " + workingPath, e); + } + } + + private Path findSonarHome(GlobalProperties props) { + String home = props.property("sonar.userHome"); + if (home != null) { + return Paths.get(home).toAbsolutePath(); + } + + home = system.envVariable("SONAR_USER_HOME"); + + if (home != null) { + return Paths.get(home).toAbsolutePath(); + } + + home = system.property("user.home"); + return Paths.get(home, ".sonar").toAbsolutePath(); + } + + private static void cleanTempFolders(Path path) throws IOException { + if (Files.exists(path)) { + try (DirectoryStream<Path> stream = Files.newDirectoryStream(path, new CleanFilter())) { + for (Path p : stream) { + deleteQuietly(p.toFile()); + } + } + } + } + + private static class CleanFilter implements DirectoryStream.Filter<Path> { + @Override + public boolean accept(Path path) throws IOException { + if (!Files.isDirectory(path)) { + return false; + } + + if (!path.getFileName().toString().startsWith(TMP_NAME_PREFIX)) { + return false; + } + + long threshold = System.currentTimeMillis() - CLEAN_MAX_AGE; + + // we could also check the timestamp in the name, instead + BasicFileAttributes attrs; + + try { + attrs = Files.readAttributes(path, BasicFileAttributes.class); + } catch (IOException ioe) { + LOG.error(String.format("Couldn't read file attributes for %s : ", path), ioe); + return false; + } + + long creationTime = attrs.creationTime().toMillis(); + return creationTime < threshold; + } + } + + @Override + public void start(PicoContainer container) { + started = true; + } + + @Override + public void stop(PicoContainer container) { + if (tempFolder != null) { + tempFolder.stop(); + } + } + + @Override + public void dispose(PicoContainer container) { + //nothing to do + } + + @Override + public boolean componentHasLifecycle() { + return true; + } + + @Override + public boolean isStarted() { + return started; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/MetricProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/MetricProvider.java new file mode 100644 index 00000000000..1735ab4a79b --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/MetricProvider.java @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import com.google.common.collect.Lists; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.ExtensionProvider; +import org.sonar.api.batch.InstantiationStrategy; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Metric; +import org.sonar.api.measures.Metrics; + +import java.util.List; + +@BatchSide +@InstantiationStrategy(InstantiationStrategy.PER_BATCH) +public class MetricProvider extends ExtensionProvider { + + private Metrics[] factories; + + public MetricProvider(Metrics[] factories) { + this.factories = factories; + } + + public MetricProvider() { + this.factories = new Metrics[0]; + } + + @Override + public List<Metric> provide() { + List<Metric> metrics = Lists.newArrayList(CoreMetrics.getMetrics()); + for (Metrics factory : factories) { + metrics.addAll(factory.getMetrics()); + } + return metrics; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/PluginInstaller.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/PluginInstaller.java new file mode 100644 index 00000000000..64f923bd9a5 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/PluginInstaller.java @@ -0,0 +1,42 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import java.util.Map; +import org.sonar.api.SonarPlugin; +import org.sonar.api.batch.BatchSide; +import org.sonar.core.platform.PluginInfo; + +@BatchSide +public interface PluginInstaller { + + /** + * Gets the list of plugins installed on server and downloads them if not + * already in local cache. + * @return information about all installed plugins, grouped by key + */ + Map<String, PluginInfo> installRemotes(); + + /** + * Used only by tests. + * @see org.sonar.batch.mediumtest.BatchMediumTester + */ + Map<String, SonarPlugin> installLocals(); +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/Slf4jLogger.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/Slf4jLogger.java new file mode 100644 index 00000000000..270bc10fe0d --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/Slf4jLogger.java @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Slf4jLogger implements org.sonar.home.cache.Logger { + + private static final Logger LOG = LoggerFactory.getLogger(Slf4jLogger.class); + + @Override + public void debug(String msg) { + LOG.debug(msg); + } + + @Override + public void info(String msg) { + LOG.info(msg); + } + + @Override + public void warn(String msg) { + LOG.warn(msg); + } + + @Override + public void error(String msg, Throwable t) { + LOG.error(msg, t); + } + + @Override + public void error(String msg) { + LOG.error(msg); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/UserProperties.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/UserProperties.java new file mode 100644 index 00000000000..d16f8e72d27 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/UserProperties.java @@ -0,0 +1,62 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import com.google.common.collect.Maps; +import org.sonar.api.config.Encryption; + +import javax.annotation.Nullable; + +import java.util.Map; + +/** + * Properties that are coming from bootstrapper. + */ +public abstract class UserProperties { + + private final Map<String, String> properties; + private final Encryption encryption; + + public UserProperties(Map<String, String> properties, @Nullable String pathToSecretKey) { + encryption = new Encryption(pathToSecretKey); + Map<String, String> decryptedProps = Maps.newHashMap(); + for (Map.Entry<String, String> entry : properties.entrySet()) { + String value = entry.getValue(); + if (value != null && encryption.isEncrypted(value)) { + try { + value = encryption.decrypt(value); + } catch (Exception e) { + throw new IllegalStateException("Fail to decrypt the property " + entry.getKey() + ". Please check your secret key.", e); + } + } + decryptedProps.put(entry.getKey(), value); + } + this.properties = Maps.newHashMap(decryptedProps); + } + + public Map<String, String> properties() { + return properties; + } + + public String property(String key) { + return properties.get(key); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/package-info.java new file mode 100644 index 00000000000..0efbab080ec --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.bootstrap; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/Batch.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/Batch.java new file mode 100644 index 00000000000..d5d3da13087 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/Batch.java @@ -0,0 +1,274 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrapper; + +import org.sonar.api.utils.MessageException; + +import com.google.common.base.Throwables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.picocontainer.annotations.Nullable; +import org.sonar.batch.bootstrap.GlobalContainer; + +/** + * Entry point for sonar-runner 2.1. + * + * @since 2.14 + */ +public final class Batch { + + private boolean started = false; + private LoggingConfiguration loggingConfig; + private List<Object> components; + private Map<String, String> bootstrapProperties = Maps.newHashMap(); + private GlobalContainer bootstrapContainer; + + private Batch(Builder builder) { + components = Lists.newArrayList(); + components.addAll(builder.components); + if (builder.environment != null) { + components.add(builder.environment); + } + if (builder.bootstrapProperties != null) { + bootstrapProperties.putAll(builder.bootstrapProperties); + } + if (builder.isEnableLoggingConfiguration()) { + loggingConfig = new LoggingConfiguration(builder.environment).setProperties(bootstrapProperties); + + if (builder.logOutput != null) { + loggingConfig.setLogOutput(builder.logOutput); + } + } + } + + public LoggingConfiguration getLoggingConfiguration() { + return loggingConfig; + } + + /** + * @deprecated since 4.4 use {@link #start()}, {@link #executeTask(Map)} and then {@link #stop()} + */ + @Deprecated + public synchronized Batch execute() { + configureLogging(); + start(); + boolean threw = true; + try { + executeTask(bootstrapProperties); + threw = false; + } finally { + doStop(threw); + } + + return this; + } + + /** + * @since 4.4 + */ + public synchronized Batch start() { + return start(false); + } + + public synchronized Batch start(boolean preferCache) { + if (started) { + throw new IllegalStateException("Batch is already started"); + } + + configureLogging(); + try { + bootstrapContainer = GlobalContainer.create(bootstrapProperties, components, preferCache); + bootstrapContainer.startComponents(); + } catch (RuntimeException e) { + throw handleException(e); + } + this.started = true; + + return this; + } + + /** + * @since 4.4 + */ + public Batch executeTask(Map<String, String> analysisProperties, Object... components) { + checkStarted(); + configureTaskLogging(analysisProperties); + try { + bootstrapContainer.executeTask(analysisProperties, components); + } catch (RuntimeException e) { + throw handleException(e); + } + return this; + } + + /** + * @since 5.2 + */ + public Batch executeTask(Map<String, String> analysisProperties, IssueListener issueListener) { + checkStarted(); + configureTaskLogging(analysisProperties); + try { + bootstrapContainer.executeTask(analysisProperties, components, issueListener); + } catch (RuntimeException e) { + throw handleException(e); + } + return this; + } + + private void checkStarted() { + if (!started) { + throw new IllegalStateException("Batch is not started. Unable to execute task."); + } + } + + private RuntimeException handleException(RuntimeException t) { + if (loggingConfig.isVerbose()) { + return t; + } + + for (Throwable y : Throwables.getCausalChain(t)) { + if (y instanceof MessageException) { + return (MessageException) y; + } + } + + return t; + } + + /** + * @since 5.2 + */ + public Batch syncProject(String projectKey) { + checkStarted(); + bootstrapContainer.syncProject(projectKey, true); + return this; + } + + /** + * @since 4.4 + */ + public synchronized void stop() { + doStop(false); + } + + private void doStop(boolean swallowException) { + checkStarted(); + configureLogging(); + try { + bootstrapContainer.stopComponents(swallowException); + } catch (RuntimeException e) { + throw handleException(e); + } + this.started = false; + } + + private void configureLogging() { + if (loggingConfig != null) { + loggingConfig.setProperties(bootstrapProperties); + LoggingConfigurator.apply(loggingConfig); + } + } + + private void configureTaskLogging(Map<String, String> taskProperties) { + if (loggingConfig != null) { + loggingConfig.setProperties(taskProperties, bootstrapProperties); + LoggingConfigurator.apply(loggingConfig); + } + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private Map<String, String> bootstrapProperties; + private EnvironmentInformation environment; + private List<Object> components = Lists.newArrayList(); + private boolean enableLoggingConfiguration = true; + private LogOutput logOutput; + + private Builder() { + } + + public Builder setEnvironment(EnvironmentInformation env) { + this.environment = env; + return this; + } + + public Builder setComponents(List<Object> l) { + this.components = l; + return this; + } + + public Builder setLogOutput(@Nullable LogOutput logOutput) { + this.logOutput = logOutput; + return this; + } + + /** + * @deprecated since 3.7 use {@link #setBootstrapProperties(Map)} + */ + @Deprecated + public Builder setGlobalProperties(Map<String, String> globalProperties) { + this.bootstrapProperties = globalProperties; + return this; + } + + public Builder setBootstrapProperties(Map<String, String> bootstrapProperties) { + this.bootstrapProperties = bootstrapProperties; + return this; + } + + public Builder addComponents(Object... components) { + Collections.addAll(this.components, components); + return this; + } + + public Builder addComponent(Object component) { + this.components.add(component); + return this; + } + + public boolean isEnableLoggingConfiguration() { + return enableLoggingConfiguration; + } + + /** + * Logback is configured by default. It can be disabled, but n this case the batch bootstrapper must provide its + * own implementation of SLF4J. + */ + public Builder setEnableLoggingConfiguration(boolean b) { + this.enableLoggingConfiguration = b; + return this; + } + + public Batch build() { + if (components == null) { + throw new IllegalStateException("Batch components are not set"); + } + return new Batch(this); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/EnvironmentInformation.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/EnvironmentInformation.java new file mode 100644 index 00000000000..a04da9df40a --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/EnvironmentInformation.java @@ -0,0 +1,59 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrapper; + +import org.sonar.api.batch.BatchSide; + +/** + * Describes execution environment. + * + * @since 2.6 + */ +@BatchSide +public class EnvironmentInformation { + + private String key; + private String version; + + public EnvironmentInformation(String key, String version) { + this.key = key; + this.version = version; + } + + /** + * @return unique key of environment, for example - "maven", "ant" + */ + public String getKey() { + return key; + } + + /** + * @return version of environment, for example Maven can have "2.2.1" or "3.0.2", + * but there is no guarantees about format - it's just a string. + */ + public String getVersion() { + return version; + } + + @Override + public String toString() { + return String.format("%s/%s", key, version); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/IssueListener.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/IssueListener.java new file mode 100644 index 00000000000..bf3f399142a --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/IssueListener.java @@ -0,0 +1,167 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrapper; + +public interface IssueListener { + void handle(Issue issue); + + class Issue { + /** @since 5.3 */ + private Integer startLine; + /** @since 5.3 */ + private Integer startLineOffset; + /** @since 5.3 */ + private Integer endLine; + /** @since 5.3 */ + private Integer endLineOffset; + + private String key; + private String componentKey; + private String message; + private String ruleKey; + private String ruleName; + private String status; + private String resolution; + private boolean isNew; + private String assigneeLogin; + private String assigneeName; + private String severity; + + public String getSeverity() { + return severity; + } + + public void setSeverity(String severity) { + this.severity = severity; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getComponentKey() { + return componentKey; + } + + public void setComponentKey(String componentKey) { + this.componentKey = componentKey; + } + + public Integer getStartLine() { + return startLine; + } + + public void setStartLine(Integer startLine) { + this.startLine = startLine; + } + + public Integer getStartLineOffset() { + return startLineOffset; + } + + public void setStartLineOffset(Integer startLineOffset) { + this.startLineOffset = startLineOffset; + } + + public Integer getEndLine() { + return endLine; + } + + public void setEndLine(Integer endLine) { + this.endLine = endLine; + } + + public Integer getEndLineOffset() { + return endLineOffset; + } + + public void setEndLineOffset(Integer endLineOffset) { + this.endLineOffset = endLineOffset; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getRuleKey() { + return ruleKey; + } + + public void setRuleKey(String ruleKey) { + this.ruleKey = ruleKey; + } + + public String getRuleName() { + return ruleName; + } + + public void setRuleName(String ruleName) { + this.ruleName = ruleName; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getResolution() { + return resolution; + } + + public void setResolution(String resolution) { + this.resolution = resolution; + } + + public boolean isNew() { + return isNew; + } + + public void setNew(boolean isNew) { + this.isNew = isNew; + } + + public String getAssigneeLogin() { + return assigneeLogin; + } + + public void setAssigneeLogin(String assigneeLogin) { + this.assigneeLogin = assigneeLogin; + } + + public String getAssigneeName() { + return assigneeName; + } + + public void setAssigneeName(String assigneeName) { + this.assigneeName = assigneeName; + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/LogCallbackAppender.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/LogCallbackAppender.java new file mode 100644 index 00000000000..183cd378711 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/LogCallbackAppender.java @@ -0,0 +1,57 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrapper; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.UnsynchronizedAppenderBase; + +public class LogCallbackAppender extends UnsynchronizedAppenderBase<ILoggingEvent> { + protected LogOutput target; + + public LogCallbackAppender(LogOutput target) { + setTarget(target); + } + + public void setTarget(LogOutput target) { + this.target = target; + } + + @Override + protected void append(ILoggingEvent event) { + target.log(event.getFormattedMessage(), translate(event.getLevel())); + } + + private static LogOutput.Level translate(Level level) { + switch (level.toInt()) { + case Level.ERROR_INT: + return LogOutput.Level.ERROR; + case Level.WARN_INT: + return LogOutput.Level.WARN; + case Level.INFO_INT: + return LogOutput.Level.INFO; + case Level.TRACE_INT: + return LogOutput.Level.TRACE; + case Level.DEBUG_INT: + default: + return LogOutput.Level.DEBUG; + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/LogOutput.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/LogOutput.java new file mode 100644 index 00000000000..b69c405d931 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/LogOutput.java @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrapper; + +/** + * Allow to redirect batch logs to a custom output. By defaults logs are written to System.out + * @since 5.2 + */ +public interface LogOutput { + + void log(String formattedMessage, Level level); + + enum Level { + ERROR, WARN, INFO, DEBUG, TRACE; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/LoggingConfiguration.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/LoggingConfiguration.java new file mode 100644 index 00000000000..41a741bbd08 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/LoggingConfiguration.java @@ -0,0 +1,153 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrapper; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Maps; + +import java.util.Map; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + +import org.apache.commons.lang.StringUtils; + +/** + * @since 2.14 + */ +public final class LoggingConfiguration { + + public static final String PROPERTY_ROOT_LOGGER_LEVEL = "ROOT_LOGGER_LEVEL"; + public static final String PROPERTY_SQL_LOGGER_LEVEL = "SQL_LOGGER_LEVEL"; + + public static final String PROPERTY_FORMAT = "FORMAT"; + + public static final String LEVEL_ROOT_VERBOSE = "DEBUG"; + public static final String LEVEL_ROOT_DEFAULT = "INFO"; + + @VisibleForTesting + static final String FORMAT_DEFAULT = "%d{HH:mm:ss.SSS} %-5level - %msg%n"; + @VisibleForTesting + static final String FORMAT_MAVEN = "[%level] [%d{HH:mm:ss.SSS}] %msg%n"; + + private Map<String, String> substitutionVariables = Maps.newHashMap(); + private LogOutput logOutput = null; + private boolean verbose; + + public LoggingConfiguration() { + this(null); + } + + public LoggingConfiguration(@Nullable EnvironmentInformation environment) { + setVerbose(false); + if (environment != null && "maven".equalsIgnoreCase(environment.getKey())) { + setFormat(FORMAT_MAVEN); + } else { + setFormat(FORMAT_DEFAULT); + } + } + + public LoggingConfiguration setProperties(Map<String, String> properties) { + setShowSql(properties, null); + setVerbose(properties, null); + return this; + } + + public LoggingConfiguration setProperties(Map<String, String> properties, @Nullable Map<String, String> fallback) { + setShowSql(properties, fallback); + setVerbose(properties, fallback); + return this; + } + + public LoggingConfiguration setLogOutput(@Nullable LogOutput listener) { + this.logOutput = listener; + return this; + } + + public LoggingConfiguration setVerbose(boolean verbose) { + return setRootLevel(verbose ? LEVEL_ROOT_VERBOSE : LEVEL_ROOT_DEFAULT); + } + + public boolean isVerbose() { + return verbose; + } + + public LoggingConfiguration setVerbose(Map<String, String> props, @Nullable Map<String, String> fallback) { + String logLevel = getFallback("sonar.log.level", props, fallback); + String deprecatedProfilingLevel = getFallback("sonar.log.profilingLevel", props, fallback); + verbose = "true".equals(getFallback("sonar.verbose", props, fallback)) || + "DEBUG".equals(logLevel) || "TRACE".equals(logLevel) || + "BASIC".equals(deprecatedProfilingLevel) || "FULL".equals(deprecatedProfilingLevel); + + return setVerbose(verbose); + } + + @CheckForNull + private static String getFallback(String key, Map<String, String> properties, @Nullable Map<String, String> fallback) { + if (properties.containsKey(key)) { + return properties.get(key); + } + + if (fallback != null) { + return fallback.get(key); + } + + return null; + } + + public LoggingConfiguration setRootLevel(String level) { + return addSubstitutionVariable(PROPERTY_ROOT_LOGGER_LEVEL, level); + } + + public LoggingConfiguration setShowSql(boolean showSql) { + return addSubstitutionVariable(PROPERTY_SQL_LOGGER_LEVEL, showSql ? "TRACE" : "WARN"); + } + + public LoggingConfiguration setShowSql(Map<String, String> properties, @Nullable Map<String, String> fallback) { + String logLevel = getFallback("sonar.log.level", properties, fallback); + String deprecatedProfilingLevel = getFallback("sonar.log.profilingLevel", properties, fallback); + boolean sql = "TRACE".equals(logLevel) || "FULL".equals(deprecatedProfilingLevel); + + return setShowSql(sql); + } + + @VisibleForTesting + LoggingConfiguration setFormat(String format) { + return addSubstitutionVariable(PROPERTY_FORMAT, StringUtils.defaultIfBlank(format, FORMAT_DEFAULT)); + } + + public LoggingConfiguration addSubstitutionVariable(String key, String value) { + substitutionVariables.put(key, value); + return this; + } + + @VisibleForTesting + String getSubstitutionVariable(String key) { + return substitutionVariables.get(key); + } + + Map<String, String> getSubstitutionVariables() { + return substitutionVariables; + } + + LogOutput getLogOutput() { + return logOutput; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/LoggingConfigurator.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/LoggingConfigurator.java new file mode 100644 index 00000000000..f7f45b1c6fe --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/LoggingConfigurator.java @@ -0,0 +1,76 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrapper; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import java.io.File; +import org.apache.commons.lang.StringUtils; +import org.slf4j.LoggerFactory; +import org.sonar.core.config.Logback; + +public class LoggingConfigurator { + private static final String CUSTOM_APPENDER_NAME = "custom_stream"; + + private LoggingConfigurator() { + } + + public static void apply(LoggingConfiguration conf, File logbackFile) { + Logback.configure(logbackFile, conf.getSubstitutionVariables()); + + if (conf.getLogOutput() != null) { + setCustomRootAppender(conf); + } + } + + public static void apply(LoggingConfiguration conf) { + apply(conf, "/org/sonar/batch/bootstrapper/logback.xml"); + } + + public static void apply(LoggingConfiguration conf, String classloaderPath) { + Logback.configure(classloaderPath, conf.getSubstitutionVariables()); + + // if not set, keep default behavior (configured to stdout through the file in classpath) + if (conf.getLogOutput() != null) { + setCustomRootAppender(conf); + } + } + + private static void setCustomRootAppender(LoggingConfiguration conf) { + Logger logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + String level = StringUtils.defaultIfBlank(conf.getSubstitutionVariables().get(LoggingConfiguration.PROPERTY_ROOT_LOGGER_LEVEL), LoggingConfiguration.LEVEL_ROOT_DEFAULT); + + if (logger.getAppender(CUSTOM_APPENDER_NAME) == null) { + logger.detachAndStopAllAppenders(); + logger.addAppender(createAppender(conf.getLogOutput())); + } + logger.setLevel(Level.toLevel(level)); + } + + private static Appender<ILoggingEvent> createAppender(LogOutput target) { + LogCallbackAppender appender = new LogCallbackAppender(target); + appender.setName(CUSTOM_APPENDER_NAME); + appender.start(); + + return appender; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/package-info.java new file mode 100644 index 00000000000..645b8d7dfe2 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/package-info.java @@ -0,0 +1,27 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +/** + * This package is a part of bootstrap process, so we should take care about backward compatibility. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.bootstrapper; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/DefaultProjectCacheStatus.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/DefaultProjectCacheStatus.java new file mode 100644 index 00000000000..45baaecacc8 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/DefaultProjectCacheStatus.java @@ -0,0 +1,80 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cache; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Date; +import org.sonar.home.cache.PersistentCache; + +import static org.sonar.core.util.FileUtils.deleteQuietly; + +public class DefaultProjectCacheStatus implements ProjectCacheStatus { + private static final String STATUS_FILENAME = "cache-sync-status"; + private PersistentCache cache; + + public DefaultProjectCacheStatus(PersistentCache cache) { + this.cache = cache; + } + + @Override + public void save() { + Date now = new Date(); + + try { + try (ObjectOutputStream objOutput = new ObjectOutputStream(new FileOutputStream(getStatusFilePath().toFile()))) { + objOutput.writeObject(now); + } + } catch (IOException e) { + throw new IllegalStateException("Failed to write cache sync status", e); + } + } + + @Override + public void delete() { + cache.clear(); + deleteQuietly(getStatusFilePath().toFile()); + } + + @Override + public Date getSyncStatus() { + Path p = getStatusFilePath(); + try { + if (!Files.isRegularFile(p)) { + return null; + } + try (ObjectInputStream objInput = new ObjectInputStream(new FileInputStream(p.toFile()))) { + return (Date) objInput.readObject(); + } + } catch (IOException | ClassNotFoundException e) { + deleteQuietly(p.toFile()); + throw new IllegalStateException("Failed to read cache sync status", e); + } + } + + private Path getStatusFilePath() { + return cache.getDirectory().resolve(STATUS_FILENAME); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/GlobalPersistentCacheProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/GlobalPersistentCacheProvider.java new file mode 100644 index 00000000000..6b8677e2ada --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/GlobalPersistentCacheProvider.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cache; + +import org.apache.commons.lang.StringUtils; +import org.sonar.batch.bootstrap.Slf4jLogger; +import org.sonar.home.cache.PersistentCacheBuilder; + +import java.nio.file.Paths; + +import org.sonar.batch.bootstrap.GlobalProperties; +import org.sonar.home.cache.PersistentCache; +import org.picocontainer.injectors.ProviderAdapter; + +public class GlobalPersistentCacheProvider extends ProviderAdapter { + private PersistentCache cache; + + public PersistentCache provide(GlobalProperties props) { + if (cache == null) { + PersistentCacheBuilder builder = new PersistentCacheBuilder(new Slf4jLogger()); + String home = props.property("sonar.userHome"); + String serverUrl = getServerUrl(props); + + if (home != null) { + builder.setSonarHome(Paths.get(home)); + } + + builder.setAreaForGlobal(serverUrl); + cache = builder.build(); + } + + return cache; + } + + private static String getServerUrl(GlobalProperties props) { + return StringUtils.removeEnd(StringUtils.defaultIfBlank(props.property("sonar.host.url"), "http://localhost:9000"), "/"); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/NonAssociatedCacheSynchronizer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/NonAssociatedCacheSynchronizer.java new file mode 100644 index 00000000000..9287c268634 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/NonAssociatedCacheSynchronizer.java @@ -0,0 +1,96 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cache; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.utils.log.Loggers; +import org.sonar.api.utils.log.Profiler; +import org.sonar.batch.repository.QualityProfileLoader; +import org.sonar.batch.rule.ActiveRulesLoader; +import org.sonar.batch.rule.RulesLoader; +import org.sonarqube.ws.QualityProfiles.SearchWsResponse.QualityProfile; + +public class NonAssociatedCacheSynchronizer { + private static final Logger LOG = LoggerFactory.getLogger(NonAssociatedCacheSynchronizer.class); + + private final ProjectCacheStatus cacheStatus; + private final QualityProfileLoader qualityProfileLoader; + private final ActiveRulesLoader activeRulesLoader; + private final RulesLoader rulesLoader; + + public NonAssociatedCacheSynchronizer(RulesLoader rulesLoader, QualityProfileLoader qualityProfileLoader, ActiveRulesLoader activeRulesLoader, ProjectCacheStatus cacheStatus) { + this.rulesLoader = rulesLoader; + this.qualityProfileLoader = qualityProfileLoader; + this.activeRulesLoader = activeRulesLoader; + this.cacheStatus = cacheStatus; + } + + public void execute(boolean force) { + Date lastSync = cacheStatus.getSyncStatus(); + + if (lastSync != null) { + if (!force) { + LOG.info("Found cache [{}]", lastSync); + return; + } else { + LOG.info("-- Found cache [{}], synchronizing data..", lastSync); + } + } else { + LOG.info("-- Cache not found, synchronizing data.."); + } + + loadData(); + cacheStatus.save(); + LOG.info("-- Succesfully synchronized cache"); + } + + private static Collection<String> getKeys(Collection<QualityProfile> qProfiles) { + List<String> list = new ArrayList<>(qProfiles.size()); + for (QualityProfile qp : qProfiles) { + list.add(qp.getKey()); + } + + return list; + } + + private void loadData() { + Profiler profiler = Profiler.create(Loggers.get(ProjectCacheSynchronizer.class)); + + profiler.startInfo("Load rules"); + rulesLoader.load(null); + profiler.stopInfo(); + + profiler.startInfo("Load default quality profiles"); + Collection<QualityProfile> qProfiles = qualityProfileLoader.loadDefault(null, null); + profiler.stopInfo(); + + profiler.startInfo("Load default active rules"); + Collection<String> keys = getKeys(qProfiles); + for (String k : keys) { + activeRulesLoader.load(k, null); + } + profiler.stopInfo(); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/ProjectCacheStatus.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/ProjectCacheStatus.java new file mode 100644 index 00000000000..ef5fd9754a1 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/ProjectCacheStatus.java @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cache; + +import java.util.Date; + +public interface ProjectCacheStatus { + void save(); + + void delete(); + + Date getSyncStatus(); +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/ProjectCacheSynchronizer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/ProjectCacheSynchronizer.java new file mode 100644 index 00000000000..64010b34c41 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/ProjectCacheSynchronizer.java @@ -0,0 +1,185 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cache; + +import com.google.common.base.Function; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.utils.log.Loggers; +import org.sonar.api.utils.log.Profiler; +import org.sonar.batch.repository.ProjectRepositories; +import org.sonar.batch.repository.ProjectRepositoriesLoader; +import org.sonar.batch.repository.QualityProfileLoader; +import org.sonar.batch.repository.ServerIssuesLoader; +import org.sonar.batch.repository.user.UserRepositoryLoader; +import org.sonar.batch.rule.ActiveRulesLoader; +import org.sonar.batch.rule.RulesLoader; +import org.sonar.scanner.protocol.input.ScannerInput.ServerIssue; +import org.sonarqube.ws.QualityProfiles.SearchWsResponse.QualityProfile; + +public class ProjectCacheSynchronizer { + private static final Logger LOG = LoggerFactory.getLogger(ProjectCacheSynchronizer.class); + + private final ServerIssuesLoader issuesLoader; + private final UserRepositoryLoader userRepository; + private final ProjectCacheStatus cacheStatus; + private final QualityProfileLoader qualityProfileLoader; + private final ProjectRepositoriesLoader projectRepositoriesLoader; + private final ActiveRulesLoader activeRulesLoader; + private final RulesLoader rulesLoader; + + public ProjectCacheSynchronizer(RulesLoader rulesLoader, QualityProfileLoader qualityProfileLoader, ProjectRepositoriesLoader projectSettingsLoader, + ActiveRulesLoader activeRulesLoader, ServerIssuesLoader issuesLoader, + UserRepositoryLoader userRepository, ProjectCacheStatus cacheStatus) { + this.rulesLoader = rulesLoader; + this.qualityProfileLoader = qualityProfileLoader; + this.projectRepositoriesLoader = projectSettingsLoader; + this.activeRulesLoader = activeRulesLoader; + this.issuesLoader = issuesLoader; + this.userRepository = userRepository; + this.cacheStatus = cacheStatus; + } + + private static boolean isToday(Date d) { + Calendar c1 = Calendar.getInstance(); + Calendar c2 = Calendar.getInstance(); + c2.setTime(d); + + return c1.get(Calendar.DAY_OF_YEAR) == c2.get(Calendar.DAY_OF_YEAR) && + c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR); + } + + private static boolean shouldUpdate(Date lastUpdate) { + return !isToday(lastUpdate); + } + + public void load(String projectKey, boolean force) { + Date lastSync = cacheStatus.getSyncStatus(); + boolean failOnError = true; + + if (lastSync != null) { + if (force) { + LOG.info("-- Found project [{}] cache [{}], synchronizing data (forced)..", projectKey, lastSync); + } else if (shouldUpdate(lastSync)) { + LOG.info("-- Found project [{}] cache [{}], synchronizing data..", projectKey, lastSync); + failOnError = false; + } else { + LOG.info("Found project [{}] cache [{}]", projectKey, lastSync); + return; + } + } else { + LOG.info("-- Cache for project [{}] not found, synchronizing data..", projectKey); + } + + try { + loadData(projectKey); + } catch (Exception e) { + if (failOnError) { + throw e; + } + + LOG.warn("-- Cache update for project [{}] failed, continuing from cache..", projectKey, e); + return; + } + + saveStatus(); + } + + private void saveStatus() { + cacheStatus.save(); + LOG.info("-- Successfully synchronized project cache"); + } + + private void loadData(String projectKey) { + Profiler profiler = Profiler.create(Loggers.get(ProjectCacheSynchronizer.class)); + + profiler.startInfo("Load rules"); + rulesLoader.load(null); + profiler.stopInfo(); + + profiler.startInfo("Load project settings"); + ProjectRepositories projectRepo = projectRepositoriesLoader.load(projectKey, true, null); + + if (!projectRepo.exists()) { + LOG.debug("Project doesn't exist in the server"); + } else if (projectRepo.lastAnalysisDate() == null) { + LOG.debug("No previous analysis found"); + } + profiler.stopInfo(); + + profiler.startInfo("Load project quality profiles"); + Collection<QualityProfile> qProfiles; + if (projectRepo.exists()) { + qProfiles = qualityProfileLoader.load(projectKey, null, null); + } else { + qProfiles = qualityProfileLoader.loadDefault(null, null); + } + profiler.stopInfo(); + + profiler.startInfo("Load project active rules"); + Collection<String> keys = getKeys(qProfiles); + for (String k : keys) { + activeRulesLoader.load(k, null); + } + profiler.stopInfo(); + + if (projectRepo.lastAnalysisDate() != null) { + profiler.startInfo("Load server issues"); + UserLoginAccumulator consumer = new UserLoginAccumulator(); + issuesLoader.load(projectKey, consumer); + profiler.stopInfo(); + + profiler.startInfo("Load user information"); + for (String login : consumer.loginSet) { + userRepository.load(login, null); + } + profiler.stopInfo("Load user information"); + } + } + + private static Collection<String> getKeys(Collection<QualityProfile> qProfiles) { + List<String> list = new ArrayList<>(qProfiles.size()); + for (QualityProfile qp : qProfiles) { + list.add(qp.getKey()); + } + + return list; + } + + private static class UserLoginAccumulator implements Function<ServerIssue, Void> { + Set<String> loginSet = new HashSet<>(); + + @Override + public Void apply(ServerIssue input) { + if (!StringUtils.isEmpty(input.getAssigneeLogin())) { + loginSet.add(input.getAssigneeLogin()); + } + return null; + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/ProjectKeySupplier.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/ProjectKeySupplier.java new file mode 100644 index 00000000000..abbd8a69485 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/ProjectKeySupplier.java @@ -0,0 +1,36 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cache; + +import org.sonar.api.batch.bootstrap.ProjectKey; + +public class ProjectKeySupplier implements ProjectKey { + private final String key; + + ProjectKeySupplier(String key) { + this.key = key; + } + + @Override + public String get() { + return key; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/ProjectPersistentCacheProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/ProjectPersistentCacheProvider.java new file mode 100644 index 00000000000..2594a006543 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/ProjectPersistentCacheProvider.java @@ -0,0 +1,67 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cache; + +import org.sonar.api.batch.bootstrap.ProjectKey; + +import org.sonar.batch.util.BatchUtils; +import org.apache.commons.lang.StringUtils; +import org.sonar.batch.bootstrap.GlobalProperties; +import com.google.common.base.Preconditions; +import org.sonar.batch.analysis.DefaultAnalysisMode; +import org.sonar.batch.bootstrap.Slf4jLogger; + +import java.nio.file.Paths; + +import org.picocontainer.injectors.ProviderAdapter; +import org.sonar.home.cache.PersistentCache; +import org.sonar.home.cache.PersistentCacheBuilder; + +public class ProjectPersistentCacheProvider extends ProviderAdapter { + private PersistentCache cache; + + public PersistentCache provide(GlobalProperties props, DefaultAnalysisMode mode, ProjectKey key) { + if (cache == null) { + PersistentCacheBuilder builder = new PersistentCacheBuilder(new Slf4jLogger()); + String projectKey = key.get(); + String home = props.property("sonar.userHome"); + String serverUrl = getServerUrl(props); + + if (home != null) { + builder.setSonarHome(Paths.get(home)); + } + + if (mode.isNotAssociated()) { + builder.setAreaForLocalProject(serverUrl, BatchUtils.getServerVersion()); + } else { + Preconditions.checkNotNull(projectKey); + builder.setAreaForProject(serverUrl, BatchUtils.getServerVersion(), projectKey); + } + + cache = builder.build(); + } + + return cache; + } + + private static String getServerUrl(GlobalProperties props) { + return StringUtils.removeEnd(StringUtils.defaultIfBlank(props.property("sonar.host.url"), "http://localhost:9000"), "/"); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/ProjectSyncContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/ProjectSyncContainer.java new file mode 100644 index 00000000000..816091786b3 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/ProjectSyncContainer.java @@ -0,0 +1,93 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cache; + +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nullable; +import org.sonar.api.CoreProperties; +import org.sonar.batch.analysis.AnalysisProperties; +import org.sonar.batch.analysis.DefaultAnalysisMode; +import org.sonar.batch.bootstrap.GlobalProperties; +import org.sonar.batch.cache.WSLoader.LoadStrategy; +import org.sonar.batch.repository.DefaultProjectRepositoriesLoader; +import org.sonar.batch.repository.DefaultQualityProfileLoader; +import org.sonar.batch.repository.DefaultServerIssuesLoader; +import org.sonar.batch.repository.ProjectRepositoriesLoader; +import org.sonar.batch.repository.QualityProfileLoader; +import org.sonar.batch.repository.ServerIssuesLoader; +import org.sonar.batch.repository.user.UserRepositoryLoader; +import org.sonar.batch.rule.ActiveRulesLoader; +import org.sonar.batch.rule.DefaultActiveRulesLoader; +import org.sonar.batch.rule.DefaultRulesLoader; +import org.sonar.batch.rule.RulesLoader; +import org.sonar.core.platform.ComponentContainer; + +public class ProjectSyncContainer extends ComponentContainer { + private final boolean force; + private final String projectKey; + + public ProjectSyncContainer(ComponentContainer globalContainer, @Nullable String projectKey, boolean force) { + super(globalContainer); + this.projectKey = projectKey; + this.force = force; + } + + @Override + public void doBeforeStart() { + addComponents(); + } + + @Override + public void doAfterStart() { + if (projectKey != null) { + getComponentByType(ProjectCacheSynchronizer.class).load(projectKey, force); + } else { + getComponentByType(NonAssociatedCacheSynchronizer.class).execute(force); + } + } + + private static DefaultAnalysisMode createIssuesAnalysisMode(@Nullable String projectKey) { + Map<String, String> props = new HashMap<>(); + props.put(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_ISSUES); + if (projectKey != null) { + props.put(CoreProperties.PROJECT_KEY_PROPERTY, projectKey); + } + GlobalProperties globalProps = new GlobalProperties(props); + AnalysisProperties analysisProps = new AnalysisProperties(props); + return new DefaultAnalysisMode(globalProps, analysisProps); + } + + private void addComponents() { + add(new StrategyWSLoaderProvider(LoadStrategy.SERVER_ONLY), + new ProjectKeySupplier(projectKey), + projectKey != null ? ProjectCacheSynchronizer.class : NonAssociatedCacheSynchronizer.class, + UserRepositoryLoader.class, + new ProjectPersistentCacheProvider(), + createIssuesAnalysisMode(projectKey)); + + addIfMissing(DefaultProjectCacheStatus.class, ProjectCacheStatus.class); + addIfMissing(DefaultProjectRepositoriesLoader.class, ProjectRepositoriesLoader.class); + addIfMissing(DefaultServerIssuesLoader.class, ServerIssuesLoader.class); + addIfMissing(DefaultQualityProfileLoader.class, QualityProfileLoader.class); + addIfMissing(DefaultRulesLoader.class, RulesLoader.class); + addIfMissing(DefaultActiveRulesLoader.class, ActiveRulesLoader.class); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/StrategyWSLoaderProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/StrategyWSLoaderProvider.java new file mode 100644 index 00000000000..d89de5ba448 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/StrategyWSLoaderProvider.java @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cache; + +import org.picocontainer.injectors.ProviderAdapter; +import org.sonar.batch.bootstrap.BatchWsClient; +import org.sonar.batch.cache.WSLoader.LoadStrategy; +import org.sonar.home.cache.PersistentCache; + +public class StrategyWSLoaderProvider extends ProviderAdapter { + private final LoadStrategy strategy; + private WSLoader wsLoader; + + public StrategyWSLoaderProvider(LoadStrategy strategy) { + this.strategy = strategy; + } + + public WSLoader provide(PersistentCache cache, BatchWsClient client) { + if (wsLoader == null) { + wsLoader = new WSLoader(strategy, cache, client); + } + return wsLoader; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/WSLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/WSLoader.java new file mode 100644 index 00000000000..328480e68bf --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/WSLoader.java @@ -0,0 +1,240 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cache; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.io.IOUtils; +import org.sonar.api.utils.MessageException; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.batch.bootstrap.BatchWsClient; +import org.sonar.home.cache.PersistentCache; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.HttpException; + +import static org.sonar.batch.cache.WSLoader.ServerStatus.ACCESSIBLE; +import static org.sonar.batch.cache.WSLoader.ServerStatus.NOT_ACCESSIBLE; +import static org.sonar.batch.cache.WSLoader.ServerStatus.UNKNOWN; + +public class WSLoader { + private static final Logger LOG = Loggers.get(WSLoader.class); + private static final String FAIL_MSG = "Server is not accessible and data is not cached"; + + public enum ServerStatus { + UNKNOWN, ACCESSIBLE, NOT_ACCESSIBLE + } + + public enum LoadStrategy { + SERVER_FIRST, CACHE_FIRST, SERVER_ONLY, CACHE_ONLY + } + + private final LoadStrategy defautLoadStrategy; + private final BatchWsClient wsClient; + private final PersistentCache cache; + private ServerStatus serverStatus; + + private DataLoader<String> stringServerLoader = new DataLoader<String>() { + @Override + public String load(String id) throws IOException { + GetRequest getRequest = new GetRequest(id); + try (Reader reader = wsClient.call(getRequest).contentReader()) { + String str = IOUtils.toString(reader); + try { + cache.put(id, str.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new IllegalStateException("Error saving to WS cache", e); + } + return str; + } + } + }; + + private DataLoader<String> stringCacheLoader = new DataLoader<String>() { + @Override + public String load(String id) throws IOException { + return cache.getString(id); + } + }; + + private DataLoader<InputStream> streamServerLoader = new DataLoader<InputStream>() { + @Override + public InputStream load(String id) throws IOException { + GetRequest getRequest = new GetRequest(id); + try (InputStream is = wsClient.call(getRequest).contentStream()) { + try { + cache.put(id, is); + } catch (IOException e) { + throw new IllegalStateException("Error saving to WS cache", e); + } + } + return cache.getStream(id); + } + }; + + private DataLoader<InputStream> streamCacheLoader = new DataLoader<InputStream>() { + @Override + public InputStream load(String id) throws IOException { + return cache.getStream(id); + } + }; + + private static class NotAvailableException extends Exception { + private static final long serialVersionUID = 1L; + + public NotAvailableException(String message) { + super(message); + } + + public NotAvailableException(Throwable cause) { + super(cause); + } + } + + public WSLoader(LoadStrategy strategy, PersistentCache cache, BatchWsClient wsClient) { + this.defautLoadStrategy = strategy; + this.serverStatus = UNKNOWN; + this.cache = cache; + this.wsClient = wsClient; + } + + @Nonnull + public WSLoaderResult<InputStream> loadStream(String id) { + return load(id, defautLoadStrategy, streamServerLoader, streamCacheLoader); + } + + @Nonnull + public WSLoaderResult<String> loadString(String id) { + return loadString(id, defautLoadStrategy); + } + + @Nonnull + public WSLoaderResult<String> loadString(String id, WSLoader.LoadStrategy strategy) { + return load(id, strategy, stringServerLoader, stringCacheLoader); + } + + @Nonnull + private <T> WSLoaderResult<T> load(String id, WSLoader.LoadStrategy strategy, DataLoader<T> serverLoader, DataLoader<T> cacheLoader) { + switch (strategy) { + case CACHE_FIRST: + return loadFromCacheFirst(id, cacheLoader, serverLoader); + case CACHE_ONLY: + return loadFromCacheFirst(id, cacheLoader, null); + case SERVER_FIRST: + return loadFromServerFirst(id, serverLoader, cacheLoader); + case SERVER_ONLY: + default: + return loadFromServerFirst(id, serverLoader, null); + } + } + + public LoadStrategy getDefaultStrategy() { + return this.defautLoadStrategy; + } + + private void switchToOffline() { + LOG.debug("server not available - switching to offline mode"); + serverStatus = NOT_ACCESSIBLE; + } + + private void switchToOnline() { + serverStatus = ACCESSIBLE; + } + + private boolean isOffline() { + return serverStatus == NOT_ACCESSIBLE; + } + + @Nonnull + private <T> WSLoaderResult<T> loadFromCacheFirst(String id, DataLoader<T> cacheLoader, @Nullable DataLoader<T> serverLoader) { + try { + return loadFromCache(id, cacheLoader); + } catch (NotAvailableException cacheNotAvailable) { + if (serverLoader != null) { + try { + return loadFromServer(id, serverLoader); + } catch (NotAvailableException serverNotAvailable) { + throw new IllegalStateException(FAIL_MSG, serverNotAvailable.getCause()); + } + } + throw new IllegalStateException("Data is not cached", cacheNotAvailable.getCause()); + } + } + + @Nonnull + private <T> WSLoaderResult<T> loadFromServerFirst(String id, DataLoader<T> serverLoader, @Nullable DataLoader<T> cacheLoader) { + try { + return loadFromServer(id, serverLoader); + } catch (NotAvailableException serverNotAvailable) { + if (cacheLoader != null) { + try { + return loadFromCache(id, cacheLoader); + } catch (NotAvailableException cacheNotAvailable) { + throw new IllegalStateException(FAIL_MSG, serverNotAvailable.getCause()); + } + } + throw new IllegalStateException("Server is not available: " + wsClient.baseUrl(), serverNotAvailable.getCause()); + } + } + + interface DataLoader<T> { + T load(String id) throws IOException; + } + + private <T> WSLoaderResult<T> loadFromCache(String id, DataLoader<T> loader) throws NotAvailableException { + T result; + + try { + result = loader.load(id); + } catch (IOException e) { + // any exception on the cache should fail fast + throw new IllegalStateException(e); + } + if (result == null) { + throw new NotAvailableException("resource not cached"); + } + return new WSLoaderResult<>(result, true); + } + + private <T> WSLoaderResult<T> loadFromServer(String id, DataLoader<T> loader) throws NotAvailableException { + if (isOffline()) { + throw new NotAvailableException("Server not available"); + } + try { + T t = loader.load(id); + switchToOnline(); + return new WSLoaderResult<>(t, false); + } catch (HttpException | MessageException e) { + // fail fast if it could connect but there was a application-level error + throw e; + } catch (IllegalStateException e) { + switchToOffline(); + throw new NotAvailableException(e); + } catch (Exception e) { + // fail fast + throw new IllegalStateException(e); + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/WSLoaderResult.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/WSLoaderResult.java new file mode 100644 index 00000000000..29db0bcb3a7 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/WSLoaderResult.java @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cache; + +import javax.annotation.Nonnull; + +public class WSLoaderResult<T> { + private T result; + private boolean fromCache; + + public WSLoaderResult(T result, boolean fromCache) { + this.result = result; + this.fromCache = fromCache; + } + + @Nonnull + public T get() { + return result; + } + + public boolean isFromCache() { + return fromCache; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/package-info.java new file mode 100644 index 00000000000..3f72a2d38b6 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cache/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.cache; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdComponents.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdComponents.java new file mode 100644 index 00000000000..30f4863783d --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdComponents.java @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cpd; + +import com.google.common.collect.ImmutableList; +import java.util.List; + +public final class CpdComponents { + + private CpdComponents() { + } + + public static List<Class<? extends Object>> all() { + return ImmutableList.of( + CpdSensor.class, + CpdMappings.class, + JavaCpdBlockIndexer.class, + DefaultCpdBlockIndexer.class); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdExecutor.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdExecutor.java new file mode 100644 index 00000000000..410938bab46 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdExecutor.java @@ -0,0 +1,217 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cpd; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.config.Settings; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.batch.cpd.index.SonarCpdBlockIndex; +import org.sonar.batch.index.BatchComponent; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.report.ReportPublisher; +import org.sonar.batch.util.ProgressReport; +import org.sonar.duplications.block.Block; +import org.sonar.duplications.detector.suffixtree.SuffixTreeCloneDetectionAlgorithm; +import org.sonar.duplications.index.CloneGroup; +import org.sonar.duplications.index.ClonePart; +import org.sonar.duplications.index.PackedMemoryCloneIndex.ResourceBlocks; +import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.scanner.protocol.output.ScannerReport.Duplicate; +import org.sonar.scanner.protocol.output.ScannerReport.Duplication; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static com.google.common.collect.FluentIterable.from; + +/** + * Runs on the root module, at the end of the project analysis. + * It executes copy paste detection involving all files of all modules, which were indexed during sensors execution for each module + * by {@link CpdSensor). The sensor is responsible for handling exclusions and block sizes. + */ +public class CpdExecutor { + private static final Logger LOG = Loggers.get(CpdExecutor.class); + // timeout for the computation of duplicates in a file (seconds) + private static final int TIMEOUT = 5 * 60; + static final int MAX_CLONE_GROUP_PER_FILE = 100; + static final int MAX_CLONE_PART_PER_GROUP = 100; + + private final SonarCpdBlockIndex index; + private final ReportPublisher publisher; + private final BatchComponentCache batchComponentCache; + private final Settings settings; + private final ExecutorService executorService; + private final ProgressReport progressReport; + private int count; + private int total; + + public CpdExecutor(Settings settings, SonarCpdBlockIndex index, ReportPublisher publisher, BatchComponentCache batchComponentCache) { + this.settings = settings; + this.index = index; + this.publisher = publisher; + this.batchComponentCache = batchComponentCache; + this.executorService = Executors.newSingleThreadExecutor(); + this.progressReport = new ProgressReport("CPD computation", TimeUnit.SECONDS.toMillis(10)); + } + + public void execute() { + total = index.noResources(); + progressReport.start(String.format("Calculating CPD for %d files", total)); + try { + Iterator<ResourceBlocks> it = index.iterator(); + + while (it.hasNext()) { + ResourceBlocks resourceBlocks = it.next(); + runCpdAnalysis(resourceBlocks.resourceId(), resourceBlocks.blocks()); + count++; + } + progressReport.stop("CPD calculation finished"); + } catch (Exception e) { + progressReport.stop(""); + throw e; + } + } + + private void runCpdAnalysis(String resource, final Collection<Block> fileBlocks) { + LOG.debug("Detection of duplications for {}", resource); + + BatchComponent component = batchComponentCache.get(resource); + if (component == null) { + LOG.error("Resource not found in component cache: {}. Skipping CPD computation for it", resource); + return; + } + + InputFile inputFile = (InputFile) component.inputComponent(); + progressReport.message(String.format("%d/%d - current file: %s", count, total, inputFile.absolutePath())); + + List<CloneGroup> duplications; + Future<List<CloneGroup>> futureResult = null; + try { + futureResult = executorService.submit(new Callable<List<CloneGroup>>() { + @Override + public List<CloneGroup> call() throws Exception { + return SuffixTreeCloneDetectionAlgorithm.detect(index, fileBlocks); + } + }); + duplications = futureResult.get(TIMEOUT, TimeUnit.SECONDS); + } catch (TimeoutException e) { + LOG.warn("Timeout during detection of duplications for " + inputFile.absolutePath()); + if (futureResult != null) { + futureResult.cancel(true); + } + return; + } catch (Exception e) { + throw new IllegalStateException("Fail during detection of duplication for " + inputFile.absolutePath(), e); + } + + List<CloneGroup> filtered; + if (!"java".equalsIgnoreCase(inputFile.language())) { + Predicate<CloneGroup> minimumTokensPredicate = DuplicationPredicates.numberOfUnitsNotLessThan(getMinimumTokens(inputFile.language())); + filtered = from(duplications).filter(minimumTokensPredicate).toList(); + } else { + filtered = duplications; + } + + saveDuplications(component, filtered); + } + + @VisibleForTesting + /** + * Not applicable to Java, as the {@link BlockChunker} that it uses does not record start and end units of each block. + * Also, it uses statements instead of tokens. + * @param languageKey + * @return + */ + int getMinimumTokens(String languageKey) { + int minimumTokens = settings.getInt("sonar.cpd." + languageKey + ".minimumTokens"); + if (minimumTokens == 0) { + minimumTokens = 100; + } + + return minimumTokens; + } + + @VisibleForTesting + final void saveDuplications(final BatchComponent component, List<CloneGroup> duplications) { + if (duplications.size() > MAX_CLONE_GROUP_PER_FILE) { + LOG.warn("Too many duplication groups on file " + component.inputComponent() + ". Keep only the first " + MAX_CLONE_GROUP_PER_FILE + + " groups."); + } + Iterable<org.sonar.scanner.protocol.output.ScannerReport.Duplication> reportDuplications = from(duplications) + .limit(MAX_CLONE_GROUP_PER_FILE) + .transform( + new Function<CloneGroup, ScannerReport.Duplication>() { + private final ScannerReport.Duplication.Builder dupBuilder = ScannerReport.Duplication.newBuilder(); + private final ScannerReport.Duplicate.Builder blockBuilder = ScannerReport.Duplicate.newBuilder(); + + @Override + public ScannerReport.Duplication apply(CloneGroup input) { + return toReportDuplication(component, dupBuilder, blockBuilder, input); + } + + }); + publisher.getWriter().writeComponentDuplications(component.batchId(), reportDuplications); + } + + private Duplication toReportDuplication(BatchComponent component, Duplication.Builder dupBuilder, Duplicate.Builder blockBuilder, CloneGroup input) { + dupBuilder.clear(); + ClonePart originBlock = input.getOriginPart(); + blockBuilder.clear(); + dupBuilder.setOriginPosition(ScannerReport.TextRange.newBuilder() + .setStartLine(originBlock.getStartLine()) + .setEndLine(originBlock.getEndLine()) + .build()); + int clonePartCount = 0; + for (ClonePart duplicate : input.getCloneParts()) { + if (!duplicate.equals(originBlock)) { + clonePartCount++; + if (clonePartCount > MAX_CLONE_PART_PER_GROUP) { + LOG.warn("Too many duplication references on file " + component.inputComponent() + " for block at line " + + originBlock.getStartLine() + ". Keep only the first " + + MAX_CLONE_PART_PER_GROUP + " references."); + break; + } + blockBuilder.clear(); + String componentKey = duplicate.getResourceId(); + if (!component.key().equals(componentKey)) { + BatchComponent sameProjectComponent = batchComponentCache.get(componentKey); + blockBuilder.setOtherFileRef(sameProjectComponent.batchId()); + } + dupBuilder.addDuplicate(blockBuilder + .setRange(ScannerReport.TextRange.newBuilder() + .setStartLine(duplicate.getStartLine()) + .setEndLine(duplicate.getEndLine()) + .build()) + .build()); + } + } + return dupBuilder.build(); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdIndexer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdIndexer.java new file mode 100644 index 00000000000..9ebf80c88a7 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdIndexer.java @@ -0,0 +1,49 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cpd; + +import org.slf4j.Logger; +import org.sonar.api.batch.BatchSide; + +@BatchSide +public abstract class CpdIndexer { + + abstract boolean isLanguageSupported(String language); + + abstract void index(String language); + + protected void logExclusions(String[] exclusions, Logger logger) { + if (exclusions.length > 0) { + StringBuilder message = new StringBuilder("Copy-paste detection exclusions:"); + for (String exclusion : exclusions) { + message.append("\n "); + message.append(exclusion); + } + + logger.info(message.toString()); + } + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdMappings.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdMappings.java new file mode 100644 index 00000000000..9a930d62fb5 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdMappings.java @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cpd; + +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.CpdMapping; + +import javax.annotation.CheckForNull; + +@BatchSide +public class CpdMappings { + + private final CpdMapping[] mappings; + + public CpdMappings(CpdMapping[] mappings) { + this.mappings = mappings; + } + + public CpdMappings() { + this(new CpdMapping[0]); + } + + @CheckForNull + public CpdMapping getMapping(String language) { + if (mappings != null) { + for (CpdMapping cpdMapping : mappings) { + if (cpdMapping.getLanguage().getKey().equals(language)) { + return cpdMapping; + } + } + } + return null; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdSensor.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdSensor.java new file mode 100644 index 00000000000..9ea1bac9254 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdSensor.java @@ -0,0 +1,99 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cpd; + +import com.google.common.annotations.VisibleForTesting; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.Phase; +import org.sonar.api.batch.fs.FileSystem; +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.config.Settings; + +@Phase(name = Phase.Name.POST) +public class CpdSensor implements Sensor { + + private static final Logger LOG = LoggerFactory.getLogger(CpdSensor.class); + + private CpdIndexer sonarEngine; + private CpdIndexer sonarBridgeEngine; + private Settings settings; + private FileSystem fs; + + public CpdSensor(JavaCpdBlockIndexer sonarEngine, DefaultCpdBlockIndexer sonarBridgeEngine, Settings settings, FileSystem fs) { + this.sonarEngine = sonarEngine; + this.sonarBridgeEngine = sonarBridgeEngine; + this.settings = settings; + this.fs = fs; + } + + @Override + public void describe(SensorDescriptor descriptor) { + descriptor.name("CPD Sensor"); + } + + @VisibleForTesting + CpdIndexer getEngine(String language) { + if (sonarEngine.isLanguageSupported(language)) { + return sonarEngine; + } + return sonarBridgeEngine; + } + + @VisibleForTesting + boolean isSkipped(String language) { + String key = "sonar.cpd." + language + ".skip"; + if (settings.hasKey(key)) { + return settings.getBoolean(key); + } + return settings.getBoolean(CoreProperties.CPD_SKIP_PROPERTY); + } + + @Override + public void execute(SensorContext context) { + if (settings.hasKey(CoreProperties.CPD_SKIP_PROPERTY)) { + LOG.warn("\"sonar.cpd.skip\" property is deprecated and will be removed. Please set \"sonar.cpd.exclusions=**\" instead to disable duplication mechanism."); + } + + for (String language : fs.languages()) { + if (settings.hasKey("sonar.cpd." + language + ".skip")) { + LOG + .warn("\"sonar.cpd." + language + ".skip\" property is deprecated and will be removed. Please set \"sonar.cpd.exclusions=**\" instead to disable duplication mechanism."); + } + + if (isSkipped(language)) { + LOG.info("Detection of duplicated code is skipped for {}", language); + continue; + } + + CpdIndexer engine = getEngine(language); + if (!engine.isLanguageSupported(language)) { + LOG.debug("Detection of duplicated code is not supported for {}", language); + continue; + } + LOG.info("{} is used for {}", engine, language); + engine.index(language); + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/DefaultCpdBlockIndexer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/DefaultCpdBlockIndexer.java new file mode 100644 index 00000000000..33b2f269595 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/DefaultCpdBlockIndexer.java @@ -0,0 +1,114 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cpd; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Lists; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.CpdMapping; +import org.sonar.api.batch.fs.FilePredicates; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.config.Settings; +import org.sonar.batch.cpd.index.SonarCpdBlockIndex; +import org.sonar.duplications.block.Block; +import org.sonar.duplications.internal.pmd.TokenizerBridge; + +public class DefaultCpdBlockIndexer extends CpdIndexer { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultCpdBlockIndexer.class); + + private final CpdMappings mappings; + private final FileSystem fs; + private final Settings settings; + private final SonarCpdBlockIndex index; + + public DefaultCpdBlockIndexer(CpdMappings mappings, FileSystem fs, Settings settings, SonarCpdBlockIndex index) { + this.mappings = mappings; + this.fs = fs; + this.settings = settings; + this.index = index; + } + + @Override + public boolean isLanguageSupported(String language) { + return true; + } + + @Override + public void index(String languageKey) { + CpdMapping mapping = mappings.getMapping(languageKey); + if (mapping == null) { + LOG.debug("No CpdMapping for language " + languageKey); + return; + } + + String[] cpdExclusions = settings.getStringArray(CoreProperties.CPD_EXCLUSIONS); + logExclusions(cpdExclusions, LOG); + FilePredicates p = fs.predicates(); + List<InputFile> sourceFiles = Lists.newArrayList(fs.inputFiles(p.and( + p.hasType(InputFile.Type.MAIN), + p.hasLanguage(languageKey), + p.doesNotMatchPathPatterns(cpdExclusions)))); + if (sourceFiles.isEmpty()) { + return; + } + + // Create index + populateIndex(languageKey, sourceFiles, mapping); + } + + private void populateIndex(String languageKey, List<InputFile> sourceFiles, CpdMapping mapping) { + TokenizerBridge bridge = new TokenizerBridge(mapping.getTokenizer(), fs.encoding().name(), getBlockSize(languageKey)); + for (InputFile inputFile : sourceFiles) { + if (!index.isIndexed(inputFile)) { + LOG.debug("Populating index from {}", inputFile); + String resourceEffectiveKey = ((DefaultInputFile) inputFile).key(); + List<Block> blocks = bridge.chunk(resourceEffectiveKey, inputFile.file()); + index.insert(inputFile, blocks); + } + } + } + + @VisibleForTesting + int getBlockSize(String languageKey) { + int blockSize = settings.getInt("sonar.cpd." + languageKey + ".minimumLines"); + if (blockSize == 0) { + blockSize = getDefaultBlockSize(languageKey); + } + return blockSize; + } + + @VisibleForTesting + public static int getDefaultBlockSize(String languageKey) { + if ("cobol".equals(languageKey)) { + return 30; + } else if ("abap".equals(languageKey)) { + return 20; + } else { + return 10; + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/DuplicationPredicates.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/DuplicationPredicates.java new file mode 100644 index 00000000000..e016ae6f970 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/DuplicationPredicates.java @@ -0,0 +1,49 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cpd; + +import com.google.common.base.Predicate; +import org.sonar.duplications.index.CloneGroup; + +import javax.annotation.Nullable; + +public final class DuplicationPredicates { + + private DuplicationPredicates() { + } + + public static Predicate<CloneGroup> numberOfUnitsNotLessThan(int min) { + return new NumberOfUnitsNotLessThan(min); + } + + private static class NumberOfUnitsNotLessThan implements Predicate<CloneGroup> { + private final int min; + + public NumberOfUnitsNotLessThan(int min) { + this.min = min; + } + + @Override + public boolean apply(@Nullable CloneGroup input) { + return input != null && input.getLengthInUnits() >= min; + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/JavaCpdBlockIndexer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/JavaCpdBlockIndexer.java new file mode 100644 index 00000000000..a2dde817c74 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/JavaCpdBlockIndexer.java @@ -0,0 +1,105 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cpd; + +import com.google.common.collect.Lists; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.fs.FilePredicates; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.config.Settings; +import org.sonar.batch.cpd.index.SonarCpdBlockIndex; +import org.sonar.duplications.block.Block; +import org.sonar.duplications.block.BlockChunker; +import org.sonar.duplications.java.JavaStatementBuilder; +import org.sonar.duplications.java.JavaTokenProducer; +import org.sonar.duplications.statement.Statement; +import org.sonar.duplications.statement.StatementChunker; +import org.sonar.duplications.token.TokenChunker; + +public class JavaCpdBlockIndexer extends CpdIndexer { + + private static final Logger LOG = LoggerFactory.getLogger(JavaCpdBlockIndexer.class); + + private static final int BLOCK_SIZE = 10; + + private final FileSystem fs; + private final Settings settings; + private final SonarCpdBlockIndex index; + + public JavaCpdBlockIndexer(FileSystem fs, Settings settings, SonarCpdBlockIndex index) { + this.fs = fs; + this.settings = settings; + this.index = index; + } + + @Override + public boolean isLanguageSupported(String language) { + return "java".equals(language); + } + + @Override + public void index(String languageKey) { + String[] cpdExclusions = settings.getStringArray(CoreProperties.CPD_EXCLUSIONS); + logExclusions(cpdExclusions, LOG); + FilePredicates p = fs.predicates(); + List<InputFile> sourceFiles = Lists.newArrayList(fs.inputFiles(p.and( + p.hasType(InputFile.Type.MAIN), + p.hasLanguage(languageKey), + p.doesNotMatchPathPatterns(cpdExclusions)))); + if (sourceFiles.isEmpty()) { + return; + } + createIndex(sourceFiles); + } + + private void createIndex(Iterable<InputFile> sourceFiles) { + TokenChunker tokenChunker = JavaTokenProducer.build(); + StatementChunker statementChunker = JavaStatementBuilder.build(); + BlockChunker blockChunker = new BlockChunker(BLOCK_SIZE); + + for (InputFile inputFile : sourceFiles) { + LOG.debug("Populating index from {}", inputFile); + String resourceEffectiveKey = ((DefaultInputFile) inputFile).key(); + + List<Statement> statements; + + try(Reader reader = new InputStreamReader(new FileInputStream(inputFile.file()), fs.encoding())) { + statements = statementChunker.chunk(tokenChunker.chunk(reader)); + } catch (FileNotFoundException e) { + throw new IllegalStateException("Cannot find file " + inputFile.file(), e); + } catch (IOException e ) { + throw new IllegalStateException("Exception hnadling file: " + inputFile.file(), e); + } + + List<Block> blocks = blockChunker.chunk(resourceEffectiveKey, statements); + index.insert(inputFile, blocks); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/index/SonarCpdBlockIndex.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/index/SonarCpdBlockIndex.java new file mode 100644 index 00000000000..e819c12e32e --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/index/SonarCpdBlockIndex.java @@ -0,0 +1,119 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cpd.index; + +import com.google.common.base.Function; +import com.google.common.collect.Iterables; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.config.Settings; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.report.ReportPublisher; +import org.sonar.duplications.block.Block; +import org.sonar.duplications.block.ByteArray; +import org.sonar.duplications.index.AbstractCloneIndex; +import org.sonar.duplications.index.CloneIndex; +import org.sonar.duplications.index.PackedMemoryCloneIndex; +import org.sonar.duplications.index.PackedMemoryCloneIndex.ResourceBlocks; +import org.sonar.scanner.protocol.output.ScannerReport; + +public class SonarCpdBlockIndex extends AbstractCloneIndex { + + private final CloneIndex mem = new PackedMemoryCloneIndex(); + private final ReportPublisher publisher; + private final BatchComponentCache batchComponentCache; + private final Settings settings; + // Files already tokenized + private final Set<InputFile> indexedFiles = new HashSet<>(); + + public SonarCpdBlockIndex(ReportPublisher publisher, BatchComponentCache batchComponentCache, Settings settings) { + this.publisher = publisher; + this.batchComponentCache = batchComponentCache; + this.settings = settings; + } + + public void insert(InputFile inputFile, Collection<Block> blocks) { + if (isCrossProjectDuplicationEnabled(settings)) { + int id = batchComponentCache.get(inputFile).batchId(); + final ScannerReport.CpdTextBlock.Builder builder = ScannerReport.CpdTextBlock.newBuilder(); + publisher.getWriter().writeCpdTextBlocks(id, Iterables.transform(blocks, new Function<Block, ScannerReport.CpdTextBlock>() { + @Override + public ScannerReport.CpdTextBlock apply(Block input) { + builder.clear(); + builder.setStartLine(input.getStartLine()); + builder.setEndLine(input.getEndLine()); + builder.setStartTokenIndex(input.getStartUnit()); + builder.setEndTokenIndex(input.getEndUnit()); + builder.setHash(input.getBlockHash().toHexString()); + return builder.build(); + } + })); + } + for (Block block : blocks) { + mem.insert(block); + } + indexedFiles.add(inputFile); + } + + public boolean isIndexed(InputFile inputFile) { + return indexedFiles.contains(inputFile); + } + + public static boolean isCrossProjectDuplicationEnabled(Settings settings) { + return settings.getBoolean(CoreProperties.CPD_CROSS_PROJECT) + // No cross project duplication for branches + && StringUtils.isBlank(settings.getString(CoreProperties.PROJECT_BRANCH_PROPERTY)); + } + + public Collection<Block> getByInputFile(String resourceKey) { + return mem.getByResourceId(resourceKey); + } + + @Override + public Collection<Block> getBySequenceHash(ByteArray hash) { + return mem.getBySequenceHash(hash); + } + + @Override + public Collection<Block> getByResourceId(String resourceId) { + throw new UnsupportedOperationException(); + } + + @Override + public void insert(Block block) { + throw new UnsupportedOperationException(); + } + + @Override + public Iterator<ResourceBlocks> iterator() { + return mem.iterator(); + } + + @Override + public int noResources() { + return mem.noResources(); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/index/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/index/package-info.java new file mode 100644 index 00000000000..86431b3a529 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/index/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.cpd.index; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/package-info.java new file mode 100644 index 00000000000..c4205b6d38e --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.cpd; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/deprecated/DeprecatedSensorContext.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/deprecated/DeprecatedSensorContext.java new file mode 100644 index 00000000000..33a56f3e5ee --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/deprecated/DeprecatedSensorContext.java @@ -0,0 +1,221 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.deprecated; + +import java.io.Serializable; +import java.util.Collection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.api.batch.SensorContext; +import org.sonar.api.batch.SonarIndex; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputDir; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.InputModule; +import org.sonar.api.batch.fs.InputPath; +import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.batch.sensor.internal.SensorStorage; +import org.sonar.api.config.Settings; +import org.sonar.api.design.Dependency; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.MeasuresFilter; +import org.sonar.api.measures.Metric; +import org.sonar.api.resources.Directory; +import org.sonar.api.resources.File; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.api.utils.SonarException; +import org.sonar.batch.sensor.DefaultSensorContext; +import org.sonar.batch.sensor.coverage.CoverageExclusions; + +public class DeprecatedSensorContext extends DefaultSensorContext implements SensorContext { + + private static final Logger LOG = LoggerFactory.getLogger(DeprecatedSensorContext.class); + + private final SonarIndex index; + private final Project project; + private final CoverageExclusions coverageFilter; + + public DeprecatedSensorContext(InputModule module, SonarIndex index, Project project, Settings settings, FileSystem fs, ActiveRules activeRules, + AnalysisMode analysisMode, CoverageExclusions coverageFilter, + SensorStorage sensorStorage) { + super(module, settings, fs, activeRules, analysisMode, sensorStorage); + this.index = index; + this.project = project; + this.coverageFilter = coverageFilter; + + } + + public Project getProject() { + return project; + } + + @Override + public boolean index(Resource resource) { + // SONAR-5006 + logWarning(); + return true; + } + + @Override + public boolean index(Resource resource, Resource parentReference) { + // SONAR-5006 + logWarning(); + return true; + } + + private static void logWarning() { + if (LOG.isDebugEnabled()) { + LOG.debug("Plugins are no more responsible for indexing physical resources like directories and files. This is now handled by the platform.", new SonarException( + "Plugin should not index physical resources")); + } + } + + @Override + public boolean isExcluded(Resource reference) { + return index.isExcluded(reference); + } + + @Override + public boolean isIndexed(Resource reference, boolean acceptExcluded) { + return index.isIndexed(reference, acceptExcluded); + } + + @Override + public Resource getParent(Resource reference) { + return index.getParent(reference); + } + + @Override + public Collection<Resource> getChildren(Resource reference) { + return index.getChildren(reference); + } + + @Override + public <G extends Serializable> Measure<G> getMeasure(Metric<G> metric) { + return index.getMeasure(project, metric); + } + + @Override + public <M> M getMeasures(MeasuresFilter<M> filter) { + return index.getMeasures(project, filter); + } + + @Override + public Measure saveMeasure(Measure measure) { + return index.addMeasure(project, measure); + } + + @Override + public Measure saveMeasure(Metric metric, Double value) { + return index.addMeasure(project, new Measure(metric, value)); + } + + @Override + public <G extends Serializable> Measure<G> getMeasure(Resource resource, Metric<G> metric) { + return index.getMeasure(resource, metric); + } + + @Override + public String saveResource(Resource resource) { + Resource persistedResource = index.addResource(resource); + if (persistedResource != null) { + return persistedResource.getEffectiveKey(); + } + return null; + } + + public boolean saveResource(Resource resource, Resource parentReference) { + return index.index(resource, parentReference); + } + + @Override + public Resource getResource(Resource resource) { + return index.getResource(resource); + } + + @Override + public <M> M getMeasures(Resource resource, MeasuresFilter<M> filter) { + return index.getMeasures(resource, filter); + } + + @Override + public Measure saveMeasure(Resource resource, Metric metric, Double value) { + Measure<?> measure = new Measure(metric, value); + coverageFilter.validate(measure, resource.getPath()); + return saveMeasure(resource, measure); + } + + @Override + public Measure saveMeasure(Resource resource, Measure measure) { + Resource resourceOrProject = resourceOrProject(resource); + + if (coverageFilter.accept(resourceOrProject, measure)) { + return index.addMeasure(resourceOrProject, measure); + } else { + return measure; + } + } + + @Override + public Dependency saveDependency(Dependency dependency) { + return null; + } + + @Override + public void saveSource(Resource reference, String source) { + // useless since 4.2. + } + + private Resource resourceOrProject(Resource resource) { + if (resource == null) { + return project; + } + Resource indexedResource = getResource(resource); + return indexedResource != null ? indexedResource : resource; + } + + @Override + public Measure saveMeasure(InputFile inputFile, Metric metric, Double value) { + Measure<?> measure = new Measure(metric, value); + coverageFilter.validate(measure, inputFile); + return saveMeasure(getResource(inputFile), measure); + } + + @Override + public Measure saveMeasure(InputFile inputFile, Measure measure) { + coverageFilter.validate(measure, inputFile); + return saveMeasure(getResource(inputFile), measure); + } + + @Override + public Resource getResource(InputPath inputPath) { + Resource r; + if (inputPath instanceof InputDir) { + r = Directory.create(((InputDir) inputPath).relativePath()); + } else if (inputPath instanceof InputFile) { + r = File.create(((InputFile) inputPath).relativePath()); + } else { + throw new IllegalArgumentException("Unknow input path type: " + inputPath); + } + return getResource(r); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/deprecated/InputFileComponent.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/deprecated/InputFileComponent.java new file mode 100644 index 00000000000..e8dcc72cb32 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/deprecated/InputFileComponent.java @@ -0,0 +1,60 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.deprecated; + +import org.sonar.api.batch.fs.InputFile.Type; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.component.Component; +import org.sonar.api.resources.Qualifiers; + +public class InputFileComponent implements Component { + + private final DefaultInputFile inputFile; + + public InputFileComponent(DefaultInputFile inputFile) { + this.inputFile = inputFile; + } + + @Override + public String key() { + return inputFile.key(); + } + + @Override + public String path() { + return inputFile.relativePath(); + } + + @Override + public String name() { + return inputFile.file().getName(); + } + + @Override + public String longName() { + return inputFile.relativePath(); + } + + @Override + public String qualifier() { + return inputFile.type() == Type.MAIN ? Qualifiers.FILE : Qualifiers.UNIT_TEST_FILE; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/deprecated/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/deprecated/package-info.java new file mode 100644 index 00000000000..9ec5890816a --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/deprecated/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.deprecated; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/deprecated/perspectives/BatchPerspectives.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/deprecated/perspectives/BatchPerspectives.java new file mode 100644 index 00000000000..ebc8465b0d3 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/deprecated/perspectives/BatchPerspectives.java @@ -0,0 +1,73 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.deprecated.perspectives; + +import com.google.common.collect.Maps; +import java.util.Map; +import javax.annotation.CheckForNull; +import org.sonar.api.batch.SonarIndex; +import org.sonar.api.batch.fs.InputPath; +import org.sonar.api.component.Perspective; +import org.sonar.api.component.ResourcePerspectives; +import org.sonar.api.resources.Resource; +import org.sonar.batch.index.BatchComponentCache; + +public class BatchPerspectives implements ResourcePerspectives { + + private final Map<Class<?>, PerspectiveBuilder<?>> builders = Maps.newHashMap(); + private final SonarIndex resourceIndex; + private final BatchComponentCache componentCache; + + public BatchPerspectives(PerspectiveBuilder[] builders, SonarIndex resourceIndex, BatchComponentCache componentCache) { + this.resourceIndex = resourceIndex; + this.componentCache = componentCache; + for (PerspectiveBuilder builder : builders) { + this.builders.put(builder.getPerspectiveClass(), builder); + } + } + + @Override + @CheckForNull + public <P extends Perspective> P as(Class<P> perspectiveClass, Resource resource) { + Resource indexedResource = resource; + if (resource.getEffectiveKey() == null) { + indexedResource = resourceIndex.getResource(resource); + } + if (indexedResource != null) { + PerspectiveBuilder<P> builder = builderFor(perspectiveClass); + return builder.loadPerspective(perspectiveClass, componentCache.get(indexedResource)); + } + return null; + } + + @Override + public <P extends Perspective> P as(Class<P> perspectiveClass, InputPath inputPath) { + PerspectiveBuilder<P> builder = builderFor(perspectiveClass); + return builder.loadPerspective(perspectiveClass, componentCache.get(inputPath)); + } + + private <T extends Perspective> PerspectiveBuilder<T> builderFor(Class<T> clazz) { + PerspectiveBuilder<T> builder = (PerspectiveBuilder<T>) builders.get(clazz); + if (builder == null) { + throw new PerspectiveNotFoundException("Perspective class is not registered: " + clazz); + } + return builder; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/deprecated/perspectives/PerspectiveBuilder.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/deprecated/perspectives/PerspectiveBuilder.java new file mode 100644 index 00000000000..0f38dc2ed41 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/deprecated/perspectives/PerspectiveBuilder.java @@ -0,0 +1,42 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.deprecated.perspectives; + +import javax.annotation.CheckForNull; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.component.Perspective; +import org.sonar.batch.index.BatchComponent; + +@BatchSide +public abstract class PerspectiveBuilder<T extends Perspective> { + + private final Class<T> perspectiveClass; + + protected PerspectiveBuilder(Class<T> perspectiveClass) { + this.perspectiveClass = perspectiveClass; + } + + public Class<T> getPerspectiveClass() { + return perspectiveClass; + } + + @CheckForNull + public abstract T loadPerspective(Class<T> perspectiveClass, BatchComponent component); +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/deprecated/perspectives/PerspectiveNotFoundException.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/deprecated/perspectives/PerspectiveNotFoundException.java new file mode 100644 index 00000000000..76dcaf532eb --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/deprecated/perspectives/PerspectiveNotFoundException.java @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.deprecated.perspectives; + +public class PerspectiveNotFoundException extends RuntimeException { + public PerspectiveNotFoundException(String message) { + super(message); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/deprecated/perspectives/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/deprecated/perspectives/package-info.java new file mode 100644 index 00000000000..1496e020dd6 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/deprecated/perspectives/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.deprecated.perspectives; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/events/BatchEvent.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/events/BatchEvent.java new file mode 100644 index 00000000000..f168bb4cd7e --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/events/BatchEvent.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.events; + +import org.sonar.api.batch.events.EventHandler; + +/** + * Root of all Sonar Batch events. + * + * @param <H> handler type + */ +public abstract class BatchEvent<H extends EventHandler> { + + protected BatchEvent() { + } + + /** + * Do not call directly - should be called only by {@link EventBus}. + * Typically should be implemented as following: <code>handler.onEvent(this)</code> + */ + protected abstract void dispatch(H handler); + + /** + * Returns class of associated handler. Used by {@link EventBus} to dispatch events to the correct handlers. + */ + protected abstract Class getType(); + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/events/BatchStepEvent.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/events/BatchStepEvent.java new file mode 100644 index 00000000000..cfa628edbf0 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/events/BatchStepEvent.java @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.events; + +import org.sonar.batch.phases.AbstractPhaseEvent; + +/** + * Generic event for some steps of project scan. + * @since 3.7 + * + */ +public class BatchStepEvent extends AbstractPhaseEvent<BatchStepHandler> + implements BatchStepHandler.BatchStepEvent { + + private String stepName; + + public BatchStepEvent(String stepName, boolean start) { + super(start); + this.stepName = stepName; + } + + @Override + public String stepName() { + return stepName; + } + + @Override + protected void dispatch(BatchStepHandler handler) { + handler.onBatchStep(this); + } + + @Override + protected Class getType() { + return BatchStepHandler.class; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/events/BatchStepHandler.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/events/BatchStepHandler.java new file mode 100644 index 00000000000..9d2cc5e54ba --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/events/BatchStepHandler.java @@ -0,0 +1,47 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.events; + +import org.sonar.api.batch.events.EventHandler; + +/** + * @since 3.7 + */ +public interface BatchStepHandler extends EventHandler { + + /** + * This interface is not intended to be implemented by clients. + */ + interface BatchStepEvent { + + String stepName(); + + boolean isStart(); + + boolean isEnd(); + + } + + /** + * Called before and after execution of each final step of project analysis + */ + void onBatchStep(BatchStepEvent event); + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/events/EventBus.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/events/EventBus.java new file mode 100644 index 00000000000..4b1629f1444 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/events/EventBus.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.events; + +import com.google.common.collect.Lists; +import org.sonar.api.batch.events.EventHandler; + +import java.util.List; + +/** + * Dispatches {@link BatchEvent}s. Eases decoupling by allowing objects to interact without having direct dependencies upon one another, and + * without requiring event sources to deal with maintaining handler lists. + */ +public class EventBus { + + private EventHandler[] registeredHandlers; + + public EventBus(EventHandler[] handlers) { + this.registeredHandlers = handlers; + } + + /** + * Fires the given event. + */ + public void fireEvent(BatchEvent event) { + doFireEvent(event); + } + + private void doFireEvent(BatchEvent event) { + List<EventHandler> handlers = getDispatchList(event.getType()); + for (EventHandler handler : handlers) { + event.dispatch(handler); + } + } + + private List<EventHandler> getDispatchList(Class<? extends EventHandler> handlerType) { + List<EventHandler> result = Lists.newArrayList(); + for (EventHandler handler : registeredHandlers) { + if (handlerType.isAssignableFrom(handler.getClass())) { + result.add(handler); + } + } + return result; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/events/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/events/package-info.java new file mode 100644 index 00000000000..922fe861be7 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/events/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.events; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/index/BatchComponent.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/index/BatchComponent.java new file mode 100644 index 00000000000..9690c833b1e --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/index/BatchComponent.java @@ -0,0 +1,84 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.index; + +import java.util.ArrayList; +import java.util.Collection; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.sonar.api.batch.fs.InputComponent; +import org.sonar.api.resources.Resource; +import org.sonar.api.resources.ResourceUtils; + +public class BatchComponent { + + private final int batchId; + private final Resource r; + private final BatchComponent parent; + private final Collection<BatchComponent> children = new ArrayList<>(); + private InputComponent inputComponent; + + public BatchComponent(int batchId, Resource r, @Nullable BatchComponent parent) { + this.batchId = batchId; + this.r = r; + this.parent = parent; + if (parent != null) { + parent.children.add(this); + } + } + + public String key() { + return r.getEffectiveKey(); + } + + public int batchId() { + return batchId; + } + + public Resource resource() { + return r; + } + + @CheckForNull + public BatchComponent parent() { + return parent; + } + + public Collection<BatchComponent> children() { + return children; + } + + public boolean isFile() { + return this.inputComponent.isFile(); + } + + public BatchComponent setInputComponent(InputComponent inputComponent) { + this.inputComponent = inputComponent; + return this; + } + + public InputComponent inputComponent() { + return inputComponent; + } + + public boolean isProjectOrModule() { + return ResourceUtils.isProject(r); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/index/BatchComponentCache.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/index/BatchComponentCache.java new file mode 100644 index 00000000000..68c29f69c95 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/index/BatchComponentCache.java @@ -0,0 +1,72 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.index; + +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.common.collect.Maps; +import java.util.Collection; +import java.util.Map; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.fs.InputComponent; +import org.sonar.api.resources.Resource; + +@BatchSide +public class BatchComponentCache { + // components by key + private final Map<String, BatchComponent> components = Maps.newLinkedHashMap(); + + private BatchComponent root; + + @CheckForNull + public BatchComponent get(String componentKey) { + return components.get(componentKey); + } + + public BatchComponent get(Resource resource) { + return components.get(resource.getEffectiveKey()); + } + + public BatchComponent get(InputComponent inputComponent) { + return components.get(inputComponent.key()); + } + + public BatchComponent add(Resource resource, @Nullable Resource parentResource) { + String componentKey = resource.getEffectiveKey(); + Preconditions.checkState(!Strings.isNullOrEmpty(componentKey), "Missing resource effective key"); + BatchComponent parent = parentResource != null ? get(parentResource.getEffectiveKey()) : null; + BatchComponent batchComponent = new BatchComponent(components.size() + 1, resource, parent); + components.put(componentKey, batchComponent); + if (parent == null) { + root = batchComponent; + } + return batchComponent; + } + + public Collection<BatchComponent> all() { + return components.values(); + } + + public BatchComponent getRoot() { + return root; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/index/Bucket.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/index/Bucket.java new file mode 100644 index 00000000000..5fde268c097 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/index/Bucket.java @@ -0,0 +1,99 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.index; + +import com.google.common.collect.Lists; +import org.sonar.api.resources.Resource; + +import javax.annotation.Nullable; + +import java.util.Collections; +import java.util.List; + +public final class Bucket { + + private Resource resource; + + private Bucket parent; + private List<Bucket> children; + + public Bucket(Resource resource) { + this.resource = resource; + } + + public Resource getResource() { + return resource; + } + + public Bucket setParent(@Nullable Bucket parent) { + this.parent = parent; + if (parent != null) { + parent.addChild(this); + } + return this; + } + + private Bucket addChild(Bucket child) { + if (children == null) { + children = Lists.newArrayList(); + } + children.add(child); + return this; + } + + private void removeChild(Bucket child) { + if (children != null) { + children.remove(child); + } + } + + public List<Bucket> getChildren() { + return children == null ? Collections.<Bucket>emptyList() : children; + } + + public Bucket getParent() { + return parent; + } + + public void clear() { + children = null; + if (parent != null) { + parent.removeChild(this); + parent = null; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Bucket that = (Bucket) o; + return resource.equals(that.resource); + } + + @Override + public int hashCode() { + return resource.hashCode(); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/index/Cache.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/index/Cache.java new file mode 100644 index 00000000000..ad194c3aad0 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/index/Cache.java @@ -0,0 +1,505 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.index; + +import com.google.common.collect.Sets; +import com.persistit.Exchange; +import com.persistit.Key; +import com.persistit.KeyFilter; +import com.persistit.exception.PersistitException; +import org.apache.commons.lang.builder.ToStringBuilder; + +import javax.annotation.CheckForNull; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * <p> + * This cache is not thread-safe, due to direct usage of {@link com.persistit.Exchange} + * </p> + */ +public class Cache<V> { + + private final String name; + private final Exchange exchange; + + Cache(String name, Exchange exchange) { + this.name = name; + this.exchange = exchange; + } + + public Cache<V> put(Object key, V value) { + resetKey(key); + return doPut(value); + } + + public Cache<V> put(Object firstKey, Object secondKey, V value) { + resetKey(firstKey, secondKey); + return doPut(value); + } + + public Cache<V> put(Object firstKey, Object secondKey, Object thirdKey, V value) { + resetKey(firstKey, secondKey, thirdKey); + return doPut(value); + } + + public Cache<V> put(Object[] key, V value) { + resetKey(key); + return doPut(value); + } + + private Cache<V> doPut(V value) { + try { + exchange.getValue().put(value); + exchange.store(); + return this; + } catch (Exception e) { + throw new IllegalStateException("Fail to put element in the cache " + name, e); + } + } + + /** + * Returns the value object associated with keys, or null if not found. + */ + public V get(Object key) { + resetKey(key); + return doGet(); + } + + /** + * Returns the value object associated with keys, or null if not found. + */ + @CheckForNull + public V get(Object firstKey, Object secondKey) { + resetKey(firstKey, secondKey); + return doGet(); + } + + /** + * Returns the value object associated with keys, or null if not found. + */ + @CheckForNull + public V get(Object firstKey, Object secondKey, Object thirdKey) { + resetKey(firstKey, secondKey, thirdKey); + return doGet(); + } + + /** + * Returns the value object associated with keys, or null if not found. + */ + @CheckForNull + public V get(Object[] key) { + resetKey(key); + return doGet(); + } + + @SuppressWarnings("unchecked") + @CheckForNull + private V doGet() { + try { + exchange.fetch(); + if (!exchange.getValue().isDefined()) { + return null; + } + return (V) exchange.getValue().get(); + } catch (Exception e) { + // TODO add parameters to message + throw new IllegalStateException("Fail to get element from cache " + name, e); + } + } + + public boolean containsKey(Object key) { + resetKey(key); + return doContainsKey(); + } + + public boolean containsKey(Object firstKey, Object secondKey) { + resetKey(firstKey, secondKey); + return doContainsKey(); + } + + public boolean containsKey(Object firstKey, Object secondKey, Object thirdKey) { + resetKey(firstKey, secondKey, thirdKey); + return doContainsKey(); + } + + public boolean containsKey(Object[] key) { + resetKey(key); + return doContainsKey(); + } + + private boolean doContainsKey() { + try { + exchange.fetch(); + return exchange.isValueDefined(); + } catch (Exception e) { + // TODO add parameters to message + throw new IllegalStateException("Fail to check if element is in cache " + name, e); + } + } + + public boolean remove(Object key) { + resetKey(key); + return doRemove(); + } + + public boolean remove(Object firstKey, Object secondKey) { + resetKey(firstKey, secondKey); + return doRemove(); + } + + public boolean remove(Object firstKey, Object secondKey, Object thirdKey) { + resetKey(firstKey, secondKey, thirdKey); + return doRemove(); + } + + public boolean remove(Object[] key) { + resetKey(key); + return doRemove(); + } + + private boolean doRemove() { + try { + return exchange.remove(); + } catch (Exception e) { + // TODO add parameters to message + throw new IllegalStateException("Fail to get element from cache " + name, e); + } + } + + /** + * Removes everything in the specified group. + * + * @param group The group name. + */ + public Cache<V> clear(Object key) { + resetKey(key); + return doClear(); + } + + public Cache<V> clear(Object firstKey, Object secondKey) { + resetKey(firstKey, secondKey); + return doClear(); + } + + public Cache<V> clear(Object firstKey, Object secondKey, Object thirdKey) { + resetKey(firstKey, secondKey, thirdKey); + return doClear(); + } + + public Cache<V> clear(Object[] key) { + resetKey(key); + return doClear(); + } + + private Cache<V> doClear() { + try { + Key to = new Key(exchange.getKey()); + to.append(Key.AFTER); + exchange.removeKeyRange(exchange.getKey(), to); + return this; + } catch (Exception e) { + throw new IllegalStateException("Fail to clear values from cache " + name, e); + } + } + + /** + * Clears the default as well as all group caches. + */ + public void clear() { + try { + exchange.clear(); + exchange.removeAll(); + } catch (Exception e) { + throw new IllegalStateException("Fail to clear cache", e); + } + } + + /** + * Returns the set of cache keys associated with this group. + * TODO implement a lazy-loading equivalent with Iterator/Iterable + * + * @param group The group. + * @return The set of cache keys for this group. + */ + @SuppressWarnings("rawtypes") + public Set keySet(Object key) { + try { + Set<Object> keys = Sets.newLinkedHashSet(); + exchange.clear(); + Exchange iteratorExchange = new Exchange(exchange); + iteratorExchange.append(key); + iteratorExchange.append(Key.BEFORE); + while (iteratorExchange.next(false)) { + keys.add(iteratorExchange.getKey().indexTo(-1).decode()); + } + return keys; + } catch (Exception e) { + throw new IllegalStateException("Fail to get keys from cache " + name, e); + } + } + + @SuppressWarnings("rawtypes") + public Set keySet(Object firstKey, Object secondKey) { + try { + Set<Object> keys = Sets.newLinkedHashSet(); + exchange.clear(); + Exchange iteratorExchange = new Exchange(exchange); + iteratorExchange.append(firstKey); + iteratorExchange.append(secondKey); + iteratorExchange.append(Key.BEFORE); + while (iteratorExchange.next(false)) { + keys.add(iteratorExchange.getKey().indexTo(-1).decode()); + } + return keys; + } catch (Exception e) { + throw new IllegalStateException("Fail to get keys from cache " + name, e); + } + } + + /** + * Returns the set of keys associated with this cache. + * + * @return The set containing the keys for this cache. + */ + public Set<Object> keySet() { + try { + Set<Object> keys = Sets.newLinkedHashSet(); + exchange.clear(); + Exchange iteratorExchange = new Exchange(exchange); + iteratorExchange.append(Key.BEFORE); + while (iteratorExchange.next(false)) { + keys.add(iteratorExchange.getKey().indexTo(-1).decode()); + } + return keys; + } catch (Exception e) { + throw new IllegalStateException("Fail to get keys from cache " + name, e); + } + } + + /** + * Lazy-loading values for given keys + */ + public Iterable<V> values(Object firstKey, Object secondKey) { + return new ValueIterable<>(exchange, firstKey, secondKey); + } + + /** + * Lazy-loading values for a given key + */ + public Iterable<V> values(Object firstKey) { + return new ValueIterable<>(exchange, firstKey); + } + + /** + * Lazy-loading values + */ + public Iterable<V> values() { + return new ValueIterable<>(exchange); + } + + public Iterable<Entry<V>> entries() { + return new EntryIterable<>(exchange); + } + + public Iterable<Entry<V>> entries(Object firstKey) { + return new EntryIterable<>(exchange, firstKey); + } + + private void resetKey(Object key) { + exchange.clear(); + exchange.append(key); + } + + private void resetKey(Object first, Object second) { + exchange.clear(); + exchange.append(first).append(second); + } + + private void resetKey(Object first, Object second, Object third) { + exchange.clear(); + exchange.append(first).append(second).append(third); + } + + private void resetKey(Object[] keys) { + exchange.clear(); + for (Object o : keys) { + exchange.append(o); + } + } + + // + // LAZY ITERATORS AND ITERABLES + // + + private static class ValueIterable<T> implements Iterable<T> { + private final Exchange originExchange; + private final Object[] keys; + + private ValueIterable(Exchange originExchange, Object... keys) { + this.originExchange = originExchange; + this.keys = keys; + } + + @Override + public Iterator<T> iterator() { + originExchange.clear(); + KeyFilter filter = new KeyFilter(); + for (Object key : keys) { + originExchange.append(key); + filter = filter.append(KeyFilter.simpleTerm(key)); + } + originExchange.append(Key.BEFORE); + Exchange iteratorExchange = new Exchange(originExchange); + return new ValueIterator<>(iteratorExchange, filter); + } + } + + private static class ValueIterator<T> implements Iterator<T> { + private final Exchange exchange; + private final KeyFilter keyFilter; + + private ValueIterator(Exchange exchange, KeyFilter keyFilter) { + this.exchange = exchange; + this.keyFilter = keyFilter; + } + + @Override + public boolean hasNext() { + try { + return exchange.hasNext(keyFilter); + } catch (PersistitException e) { + throw new IllegalStateException(e); + } + } + + @SuppressWarnings("unchecked") + @Override + public T next() { + try { + exchange.next(keyFilter); + } catch (PersistitException e) { + throw new IllegalStateException(e); + } + if (exchange.getValue().isDefined()) { + return (T) exchange.getValue().get(); + } + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Removing an item is not supported"); + } + } + + private static class EntryIterable<T> implements Iterable<Entry<T>> { + private final Exchange originExchange; + private final Object[] keys; + + private EntryIterable(Exchange originExchange, Object... keys) { + this.originExchange = originExchange; + this.keys = keys; + } + + @Override + public Iterator<Entry<T>> iterator() { + originExchange.clear(); + KeyFilter filter = new KeyFilter(); + for (Object key : keys) { + originExchange.append(key); + filter = filter.append(KeyFilter.simpleTerm(key)); + } + originExchange.append(Key.BEFORE); + Exchange iteratorExchange = new Exchange(originExchange); + return new EntryIterator<>(iteratorExchange, filter); + } + } + + private static class EntryIterator<T> implements Iterator<Entry<T>> { + private final Exchange exchange; + private final KeyFilter keyFilter; + + private EntryIterator(Exchange exchange, KeyFilter keyFilter) { + this.exchange = exchange; + this.keyFilter = keyFilter; + } + + @Override + public boolean hasNext() { + try { + return exchange.hasNext(keyFilter); + } catch (PersistitException e) { + throw new IllegalStateException(e); + } + } + + @SuppressWarnings("unchecked") + @Override + public Entry<T> next() { + try { + exchange.next(keyFilter); + } catch (PersistitException e) { + throw new IllegalStateException(e); + } + if (exchange.getValue().isDefined()) { + T value = (T) exchange.getValue().get(); + Key key = exchange.getKey(); + Object[] array = new Object[key.getDepth()]; + for (int i = 0; i < key.getDepth(); i++) { + array[i] = key.indexTo(i - key.getDepth()).decode(); + } + return new Entry<>(array, value); + } + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Removing an item is not supported"); + } + } + + public static class Entry<V> { + private final Object[] key; + private final V value; + + Entry(Object[] key, V value) { + this.key = key; + this.value = value; + } + + public Object[] key() { + return key; + } + + public V value() { + return value; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/index/Caches.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/index/Caches.java new file mode 100644 index 00000000000..ddddb271dfd --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/index/Caches.java @@ -0,0 +1,100 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.index; + +import com.google.common.collect.Maps; + +import java.util.Map; +import java.util.Map.Entry; + +import com.google.common.base.Preconditions; +import com.persistit.Exchange; +import com.persistit.Value; +import com.persistit.encoding.CoderManager; +import com.persistit.Persistit; +import com.persistit.encoding.ValueCoder; +import com.persistit.exception.PersistitException; +import com.persistit.Volume; +import org.picocontainer.Startable; +import org.sonar.api.batch.BatchSide; + +@BatchSide +public class Caches implements Startable { + private final Map<String, Exchange> cacheMap = Maps.newHashMap(); + private Persistit persistit; + private Volume volume; + + public Caches(CachesManager caches) { + persistit = caches.persistit(); + doStart(); + } + + @Override + public void start() { + // done in constructor + } + + private void doStart() { + try { + persistit.flush(); + volume = persistit.createTemporaryVolume(); + } catch (Exception e) { + throw new IllegalStateException("Fail to create a cache volume", e); + } + } + + public void registerValueCoder(Class<?> clazz, ValueCoder coder) { + CoderManager cm = persistit.getCoderManager(); + cm.registerValueCoder(clazz, coder); + } + + public <V> Cache<V> createCache(String cacheName) { + Preconditions.checkState(volume != null && volume.isOpened(), "Caches are not initialized"); + Preconditions.checkState(!cacheMap.containsKey(cacheName), "Cache is already created: " + cacheName); + try { + Exchange exchange = persistit.getExchange(volume, cacheName, true); + exchange.setMaximumValueSize(Value.MAXIMUM_SIZE); + Cache<V> cache = new Cache<>(cacheName, exchange); + cacheMap.put(cacheName, exchange); + return cache; + } catch (Exception e) { + throw new IllegalStateException("Fail to create cache: " + cacheName, e); + } + } + + @Override + public void stop() { + for (Entry<String, Exchange> e : cacheMap.entrySet()) { + persistit.releaseExchange(e.getValue()); + } + + cacheMap.clear(); + + if (volume != null) { + try { + volume.close(); + volume.delete(); + } catch (PersistitException e) { + throw new IllegalStateException("Fail to close caches", e); + } + volume = null; + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/index/CachesManager.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/index/CachesManager.java new file mode 100644 index 00000000000..010375a84eb --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/index/CachesManager.java @@ -0,0 +1,98 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.index; + +import com.persistit.Persistit; +import com.persistit.exception.PersistitException; +import com.persistit.logging.Slf4jAdapter; +import java.io.File; +import java.util.Properties; +import org.picocontainer.Startable; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.utils.TempFolder; + +import static org.sonar.core.util.FileUtils.deleteQuietly; + +/** + * Factory of caches + * + * @since 3.6 + */ +@BatchSide +public class CachesManager implements Startable { + private File tempDir; + private Persistit persistit; + private final TempFolder tempFolder; + + public CachesManager(TempFolder tempFolder) { + this.tempFolder = tempFolder; + initPersistit(); + } + + private void initPersistit() { + try { + tempDir = tempFolder.newDir("caches"); + persistit = new Persistit(); + persistit.setPersistitLogger(new Slf4jAdapter(LoggerFactory.getLogger("PERSISTIT"))); + Properties props = new Properties(); + props.setProperty("datapath", tempDir.getAbsolutePath()); + props.setProperty("logpath", "${datapath}/log"); + props.setProperty("logfile", "${logpath}/persistit_${timestamp}.log"); + props.setProperty("buffer.count.8192", "10"); + props.setProperty("journalpath", "${datapath}/journal"); + props.setProperty("tmpvoldir", "${datapath}"); + props.setProperty("volume.1", "${datapath}/persistit,create,pageSize:8192,initialPages:10,extensionPages:100,maximumPages:25000"); + props.setProperty("jmx", "false"); + persistit.setProperties(props); + persistit.initialize(); + + } catch (Exception e) { + throw new IllegalStateException("Fail to start caches", e); + } + } + + @Override + public void start() { + // already started in constructor + } + + @Override + public void stop() { + if (persistit != null) { + try { + persistit.close(false); + persistit = null; + } catch (PersistitException e) { + throw new IllegalStateException("Fail to close caches", e); + } + } + deleteQuietly(tempDir); + tempDir = null; + } + + File tempDir() { + return tempDir; + } + + Persistit persistit() { + return persistit; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/index/DefaultIndex.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/index/DefaultIndex.java new file mode 100644 index 00000000000..d5f1a561729 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/index/DefaultIndex.java @@ -0,0 +1,353 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.index; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.ObjectUtils; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.SonarIndex; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.fs.internal.DefaultInputModule; +import org.sonar.api.design.Dependency; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.MeasuresFilter; +import org.sonar.api.measures.MeasuresFilters; +import org.sonar.api.resources.Directory; +import org.sonar.api.resources.File; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.resources.Resource; +import org.sonar.api.resources.ResourceUtils; +import org.sonar.api.scan.filesystem.PathResolver; +import org.sonar.batch.DefaultProjectTree; +import org.sonar.batch.scan.measure.MeasureCache; +import org.sonar.batch.sensor.DefaultSensorStorage; +import org.sonar.core.component.ComponentKeys; + +public class DefaultIndex extends SonarIndex { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultIndex.class); + + private final BatchComponentCache componentCache; + private final MeasureCache measureCache; + private final PathResolver pathResolver; + private final DefaultProjectTree projectTree; + // caches + private DefaultSensorStorage sensorStorage; + private Project currentProject; + private Map<Resource, Bucket> buckets = Maps.newLinkedHashMap(); + + public DefaultIndex(BatchComponentCache componentCache, DefaultProjectTree projectTree, MeasureCache measureCache, PathResolver pathResolver) { + this.componentCache = componentCache; + this.projectTree = projectTree; + this.measureCache = measureCache; + this.pathResolver = pathResolver; + } + + public void start() { + Project rootProject = projectTree.getRootProject(); + if (StringUtils.isNotBlank(rootProject.getKey())) { + doStart(rootProject); + } + } + + void doStart(Project rootProject) { + Bucket bucket = new Bucket(rootProject); + addBucket(rootProject, bucket); + BatchComponent component = componentCache.add(rootProject, null); + component.setInputComponent(new DefaultInputModule(rootProject.getEffectiveKey())); + currentProject = rootProject; + + for (Project module : rootProject.getModules()) { + addModule(rootProject, module); + } + } + + private void addBucket(Resource resource, Bucket bucket) { + buckets.put(resource, bucket); + } + + private void addModule(Project parent, Project module) { + ProjectDefinition parentDefinition = projectTree.getProjectDefinition(parent); + java.io.File parentBaseDir = parentDefinition.getBaseDir(); + ProjectDefinition moduleDefinition = projectTree.getProjectDefinition(module); + java.io.File moduleBaseDir = moduleDefinition.getBaseDir(); + module.setPath(new PathResolver().relativePath(parentBaseDir, moduleBaseDir)); + addResource(module); + for (Project submodule : module.getModules()) { + addModule(module, submodule); + } + } + + @Override + public Project getProject() { + return currentProject; + } + + public void setCurrentProject(Project project, DefaultSensorStorage sensorStorage) { + this.currentProject = project; + + // the following components depend on the current module, so they need to be reloaded. + this.sensorStorage = sensorStorage; + } + + /** + * Keep only project stuff + */ + public void clear() { + Iterator<Map.Entry<Resource, Bucket>> it = buckets.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<Resource, Bucket> entry = it.next(); + Resource resource = entry.getKey(); + if (!ResourceUtils.isSet(resource)) { + entry.getValue().clear(); + it.remove(); + } + + } + } + + @CheckForNull + @Override + public Measure getMeasure(Resource resource, org.sonar.api.batch.measure.Metric<?> metric) { + return getMeasures(resource, MeasuresFilters.metric(metric)); + } + + @CheckForNull + @Override + public <M> M getMeasures(Resource resource, MeasuresFilter<M> filter) { + // Reload resource so that effective key is populated + Resource indexedResource = getResource(resource); + if (indexedResource == null) { + return null; + } + Collection<Measure> unfiltered = new ArrayList<>(); + if (filter instanceof MeasuresFilters.MetricFilter) { + // optimization + Measure byMetric = measureCache.byMetric(indexedResource, ((MeasuresFilters.MetricFilter<M>) filter).filterOnMetricKey()); + if (byMetric != null) { + unfiltered.add(byMetric); + } + } else { + for (Measure measure : measureCache.byResource(indexedResource)) { + unfiltered.add(measure); + } + } + return filter.filter(unfiltered); + } + + @Override + public Measure addMeasure(Resource resource, Measure measure) { + Bucket bucket = getBucket(resource); + if (bucket != null) { + return sensorStorage.saveMeasure(resource, measure); + } + return measure; + } + + @Override + public Dependency addDependency(Dependency dependency) { + return dependency; + } + + @Override + public Set<Resource> getResources() { + return buckets.keySet(); + } + + @Override + public String getSource(Resource reference) { + Resource resource = getResource(reference); + if (resource instanceof File) { + File file = (File) resource; + Project module = currentProject; + ProjectDefinition def = projectTree.getProjectDefinition(module); + try { + return FileUtils.readFileToString(new java.io.File(def.getBaseDir(), file.getPath())); + } catch (IOException e) { + throw new IllegalStateException("Unable to read file content " + reference, e); + } + } + return null; + } + + /** + * Does nothing if the resource is already registered. + */ + @Override + public Resource addResource(Resource resource) { + Bucket bucket = doIndex(resource); + return bucket != null ? bucket.getResource() : null; + } + + @Override + @CheckForNull + public <R extends Resource> R getResource(@Nullable R reference) { + Bucket bucket = getBucket(reference); + if (bucket != null) { + return (R) bucket.getResource(); + } + return null; + } + + @Override + public List<Resource> getChildren(Resource resource) { + List<Resource> children = Lists.newLinkedList(); + Bucket bucket = getBucket(resource); + if (bucket != null) { + for (Bucket childBucket : bucket.getChildren()) { + children.add(childBucket.getResource()); + } + } + return children; + } + + @Override + public Resource getParent(Resource resource) { + Bucket bucket = getBucket(resource); + if (bucket != null && bucket.getParent() != null) { + return bucket.getParent().getResource(); + } + return null; + } + + @Override + public boolean index(Resource resource) { + Bucket bucket = doIndex(resource); + return bucket != null; + } + + private Bucket doIndex(Resource resource) { + if (resource.getParent() != null) { + doIndex(resource.getParent()); + } + return doIndex(resource, resource.getParent()); + } + + @Override + public boolean index(Resource resource, Resource parentReference) { + Bucket bucket = doIndex(resource, parentReference); + return bucket != null; + } + + private Bucket doIndex(Resource resource, @Nullable Resource parentReference) { + Bucket bucket = getBucket(resource); + if (bucket != null) { + return bucket; + } + + if (StringUtils.isBlank(resource.getKey())) { + LOG.warn("Unable to index a resource without key " + resource); + return null; + } + + Resource parent = (Resource) ObjectUtils.defaultIfNull(parentReference, currentProject); + + Bucket parentBucket = getBucket(parent); + if (parentBucket == null && parent != null) { + LOG.warn("Resource ignored, parent is not indexed: " + resource); + return null; + } + + if (ResourceUtils.isProject(resource) || /* For technical projects */ResourceUtils.isRootProject(resource)) { + resource.setEffectiveKey(resource.getKey()); + } else { + resource.setEffectiveKey(ComponentKeys.createEffectiveKey(currentProject, resource)); + } + bucket = new Bucket(resource).setParent(parentBucket); + addBucket(resource, bucket); + + Resource parentResource = parentBucket != null ? parentBucket.getResource() : null; + BatchComponent component = componentCache.add(resource, parentResource); + if (ResourceUtils.isProject(resource)) { + component.setInputComponent(new DefaultInputModule(resource.getEffectiveKey())); + } + + return bucket; + } + + @Override + public boolean isExcluded(@Nullable Resource reference) { + return false; + } + + @Override + public boolean isIndexed(@Nullable Resource reference, boolean acceptExcluded) { + return getBucket(reference) != null; + } + + private Bucket getBucket(@Nullable Resource reference) { + if (reference == null) { + return null; + } + if (StringUtils.isNotBlank(reference.getKey())) { + return buckets.get(reference); + } + String relativePathFromSourceDir = null; + boolean isTest = false; + boolean isDir = false; + if (reference instanceof File) { + File referenceFile = (File) reference; + isTest = Qualifiers.UNIT_TEST_FILE.equals(referenceFile.getQualifier()); + relativePathFromSourceDir = referenceFile.relativePathFromSourceDir(); + } else if (reference instanceof Directory) { + isDir = true; + Directory referenceDir = (Directory) reference; + relativePathFromSourceDir = referenceDir.relativePathFromSourceDir(); + if (Directory.ROOT.equals(relativePathFromSourceDir)) { + relativePathFromSourceDir = ""; + } + } + if (relativePathFromSourceDir != null) { + // Resolve using deprecated key + List<String> dirs; + ProjectDefinition projectDef = projectTree.getProjectDefinition(getProject()); + if (isTest) { + dirs = projectDef.getTestDirs(); + } else { + dirs = projectDef.getSourceDirs(); + } + for (String src : dirs) { + java.io.File dirOrFile = pathResolver.relativeFile(projectDef.getBaseDir(), src); + java.io.File abs = new java.io.File(dirOrFile, relativePathFromSourceDir); + Bucket b = getBucket(isDir ? Directory.fromIOFile(abs, getProject()) : File.fromIOFile(abs, getProject())); + if (b != null) { + return b; + } + } + + } + return null; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/index/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/index/package-info.java new file mode 100644 index 00000000000..6fc80086043 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/index/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.index; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DefaultFilterableIssue.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DefaultFilterableIssue.java new file mode 100644 index 00000000000..12dac2c1902 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DefaultFilterableIssue.java @@ -0,0 +1,92 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import java.util.Date; +import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; +import org.sonar.api.resources.Project; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.scan.issue.filter.FilterableIssue; +import org.sonar.scanner.protocol.output.ScannerReport.Issue; + +public class DefaultFilterableIssue implements FilterableIssue { + private final Issue rawIssue; + private final Project project; + private final String componentKey; + + public DefaultFilterableIssue(Project project, Issue rawIssue, String componentKey) { + this.project = project; + this.rawIssue = rawIssue; + this.componentKey = componentKey; + + } + + @Override + public String componentKey() { + return componentKey; + } + + @Override + public RuleKey ruleKey() { + return RuleKey.of(rawIssue.getRuleRepository(), rawIssue.getRuleKey()); + } + + @Override + public String severity() { + return rawIssue.getSeverity().name(); + } + + @Override + public String message() { + return rawIssue.getMsg(); + } + + @Override + public Integer line() { + return rawIssue.hasLine() ? rawIssue.getLine() : null; + } + + @Override + public Double gap() { + return rawIssue.hasGap() ? rawIssue.getGap() : null; + } + + @Override + public Double effortToFix() { + return gap(); + } + + @Override + public Date creationDate() { + return project.getAnalysisDate(); + } + + @Override + public String projectKey() { + return project.getEffectiveKey(); + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DefaultIssuable.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DefaultIssuable.java new file mode 100644 index 00000000000..291fb7bb2ee --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DefaultIssuable.java @@ -0,0 +1,65 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import java.util.Collections; +import java.util.List; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.issue.internal.DefaultIssue; +import org.sonar.api.issue.Issuable; +import org.sonar.api.issue.Issue; +import org.sonar.batch.index.BatchComponent; + +/** + * @since 3.6 + */ +public class DefaultIssuable implements Issuable { + + private final BatchComponent component; + private final SensorContext sensorContext; + + DefaultIssuable(BatchComponent component, SensorContext sensorContext) { + this.component = component; + this.sensorContext = sensorContext; + } + + @Override + public IssueBuilder newIssueBuilder() { + DefaultIssue newIssue = (DefaultIssue) sensorContext.newIssue(); + return new DeprecatedIssueBuilderWrapper(component.inputComponent(), newIssue); + } + + @Override + public boolean addIssue(Issue issue) { + ((DeprecatedIssueWrapper) issue).wrapped().save(); + return true; + } + + @Override + public List<Issue> resolvedIssues() { + return Collections.emptyList(); + } + + @Override + public List<Issue> issues() { + return Collections.emptyList(); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DefaultIssueCallback.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DefaultIssueCallback.java new file mode 100644 index 00000000000..4c6a3b5c1e4 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DefaultIssueCallback.java @@ -0,0 +1,121 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import org.sonar.batch.issue.tracking.TrackedIssue; + +import org.apache.commons.lang.StringUtils; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.batch.rule.Rule; +import org.sonar.api.batch.rule.Rules; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.sonar.batch.repository.user.UserRepositoryLoader; +import org.sonar.scanner.protocol.input.ScannerInput.User; +import org.sonar.batch.bootstrapper.IssueListener; + +public class DefaultIssueCallback implements IssueCallback { + private final IssueCache issues; + private final IssueListener listener; + private final UserRepositoryLoader userRepository; + private final Rules rules; + + private Set<String> userLoginNames = new HashSet<>(); + private Map<String, String> userMap = new HashMap<>(); + private Set<RuleKey> ruleKeys = new HashSet<>(); + + public DefaultIssueCallback(IssueCache issues, IssueListener listener, UserRepositoryLoader userRepository, Rules rules) { + this.issues = issues; + this.listener = listener; + this.userRepository = userRepository; + this.rules = rules; + } + + /** + * If no listener exists, this constructor will be used by pico. + */ + public DefaultIssueCallback(IssueCache issues, UserRepositoryLoader userRepository, Rules rules) { + this(issues, null, userRepository, rules); + } + + @Override + public void execute() { + if (listener == null) { + return; + } + + for (TrackedIssue issue : issues.all()) { + collectInfo(issue); + } + + getUsers(); + + for (TrackedIssue issue : issues.all()) { + IssueListener.Issue newIssue = new IssueListener.Issue(); + newIssue.setAssigneeLogin(issue.assignee()); + newIssue.setAssigneeName(getAssigneeName(issue.assignee())); + newIssue.setComponentKey(issue.componentKey()); + newIssue.setKey(issue.key()); + newIssue.setMessage(issue.getMessage()); + newIssue.setNew(issue.isNew()); + newIssue.setResolution(issue.resolution()); + newIssue.setRuleKey(issue.getRuleKey().toString()); + newIssue.setRuleName(getRuleName(issue.getRuleKey())); + newIssue.setSeverity(issue.severity()); + newIssue.setStatus(issue.status()); + newIssue.setStartLine(issue.startLine()); + newIssue.setStartLineOffset(issue.startLineOffset()); + newIssue.setEndLine(issue.endLine()); + newIssue.setEndLineOffset(issue.endLineOffset()); + + listener.handle(newIssue); + } + } + + private void collectInfo(TrackedIssue issue) { + if (!StringUtils.isEmpty(issue.assignee())) { + userLoginNames.add(issue.assignee()); + } + if (issue.getRuleKey() != null) { + ruleKeys.add(issue.getRuleKey()); + } + } + + private String getAssigneeName(String login) { + return userMap.get(login); + } + + private void getUsers() { + for (String loginName : userLoginNames) { + User user = userRepository.load(loginName); + if (user != null) { + userMap.put(user.getLogin(), user.getName()); + } + } + } + + private String getRuleName(RuleKey ruleKey) { + Rule rule = rules.find(ruleKey); + return rule != null ? rule.name() : null; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DefaultIssueFilterChain.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DefaultIssueFilterChain.java new file mode 100644 index 00000000000..2d9b86ad4a8 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DefaultIssueFilterChain.java @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import com.google.common.collect.ImmutableList; +import org.sonar.api.scan.issue.filter.IssueFilter; + +import java.util.List; + +import org.sonar.api.scan.issue.filter.FilterableIssue; +import org.sonar.api.scan.issue.filter.IssueFilterChain; + +public class DefaultIssueFilterChain implements IssueFilterChain { + private final List<IssueFilter> filters; + + public DefaultIssueFilterChain(IssueFilter... filters) { + this.filters = ImmutableList.copyOf(filters); + } + + public DefaultIssueFilterChain() { + this.filters = ImmutableList.of(); + } + + private DefaultIssueFilterChain(List<IssueFilter> filters) { + this.filters = filters; + } + + @Override + public boolean accept(FilterableIssue issue) { + if (filters.isEmpty()) { + return true; + } else { + return filters.get(0).accept(issue, new DefaultIssueFilterChain(filters.subList(1, filters.size()))); + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DefaultProjectIssues.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DefaultProjectIssues.java new file mode 100644 index 00000000000..be81a1a2a53 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DefaultProjectIssues.java @@ -0,0 +1,79 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.ProjectIssues; +import org.sonar.batch.issue.tracking.TrackedIssue; + +import javax.annotation.Nullable; + +/** + * Expose list of issues for the current project + * @since 4.0 + */ +public class DefaultProjectIssues implements ProjectIssues { + + private final IssueCache cache; + + public DefaultProjectIssues(IssueCache cache) { + this.cache = cache; + } + + @Override + public Iterable<Issue> issues() { + return Iterables.transform( + Iterables.filter(cache.all(), new ResolvedPredicate(false)), + new IssueTransformer()); + } + + @Override + public Iterable<Issue> resolvedIssues() { + return Iterables.transform( + Iterables.filter(cache.all(), new ResolvedPredicate(true)), + new IssueTransformer()); + } + + private static class ResolvedPredicate implements Predicate<TrackedIssue> { + private final boolean resolved; + + private ResolvedPredicate(boolean resolved) { + this.resolved = resolved; + } + + @Override + public boolean apply(@Nullable TrackedIssue issue) { + if (issue != null) { + return resolved ? (issue.resolution() != null) : (issue.resolution() == null); + } + return false; + } + } + + private static class IssueTransformer implements Function<TrackedIssue, Issue> { + @Override + public Issue apply(TrackedIssue issue) { + return new TrackedIssueAdapter(issue); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DeprecatedIssueAdapterForFilter.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DeprecatedIssueAdapterForFilter.java new file mode 100644 index 00000000000..9dfa16d7f7f --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DeprecatedIssueAdapterForFilter.java @@ -0,0 +1,193 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.IssueComment; +import org.sonar.api.resources.Project; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.utils.Duration; + +/** + * @deprecated since 5.3 + */ +@Deprecated +class DeprecatedIssueAdapterForFilter implements Issue { + private final Project project; + private final org.sonar.scanner.protocol.output.ScannerReport.Issue rawIssue; + private final String componentKey; + + DeprecatedIssueAdapterForFilter(Project project, org.sonar.scanner.protocol.output.ScannerReport.Issue rawIssue, String componentKey) { + this.project = project; + this.rawIssue = rawIssue; + this.componentKey = componentKey; + } + + @Override + public String key() { + throw unsupported(); + } + + @Override + public String componentKey() { + return componentKey; + } + + @Override + public RuleKey ruleKey() { + return RuleKey.of(rawIssue.getRuleRepository(), rawIssue.getRuleKey()); + } + + @Override + public String language() { + throw unsupported(); + } + + @Override + public String severity() { + return rawIssue.getSeverity().name(); + } + + @Override + public String message() { + return rawIssue.getMsg(); + } + + @Override + public Integer line() { + return rawIssue.hasLine() ? rawIssue.getLine() : null; + } + + @Override + @Deprecated + public Double effortToFix() { + return gap(); + } + + @Override + public Double gap() { + return rawIssue.hasGap() ? rawIssue.getGap() : null; + } + + @Override + public String status() { + return Issue.STATUS_OPEN; + } + + @Override + public String resolution() { + return null; + } + + @Override + public String reporter() { + throw unsupported(); + } + + @Override + public String assignee() { + return null; + } + + @Override + public Date creationDate() { + return project.getAnalysisDate(); + } + + @Override + public Date updateDate() { + return null; + } + + @Override + public Date closeDate() { + return null; + } + + @Override + public String attribute(String key) { + return attributes().get(key); + } + + @Override + public Map<String, String> attributes() { + return Collections.emptyMap(); + } + + @Override + public String authorLogin() { + throw unsupported(); + } + + @Override + public String actionPlanKey() { + throw unsupported(); + } + + @Override + public List<IssueComment> comments() { + throw unsupported(); + } + + @Override + public boolean isNew() { + throw unsupported(); + } + + @Deprecated + @Override + public Duration debt() { + return effort(); + } + + @Override + public Duration effort() { + throw unsupported(); + } + + @Override + public String projectKey() { + return project.getEffectiveKey(); + } + + @Override + public String projectUuid() { + throw unsupported(); + } + + @Override + public String componentUuid() { + throw unsupported(); + } + + @Override + public Collection<String> tags() { + throw unsupported(); + } + + private static UnsupportedOperationException unsupported() { + return new UnsupportedOperationException("Not available for issues filters"); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DeprecatedIssueBuilderWrapper.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DeprecatedIssueBuilderWrapper.java new file mode 100644 index 00000000000..0ebef7b6c73 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DeprecatedIssueBuilderWrapper.java @@ -0,0 +1,135 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import com.google.common.base.Preconditions; +import javax.annotation.Nullable; +import org.sonar.api.batch.fs.InputComponent; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.TextRange; +import org.sonar.api.batch.rule.Severity; +import org.sonar.api.batch.sensor.issue.NewIssueLocation; +import org.sonar.api.batch.sensor.issue.internal.DefaultIssue; +import org.sonar.api.batch.sensor.issue.internal.DefaultIssueLocation; +import org.sonar.api.issue.Issuable; +import org.sonar.api.issue.Issuable.IssueBuilder; +import org.sonar.api.issue.Issue; +import org.sonar.api.rule.RuleKey; + +public class DeprecatedIssueBuilderWrapper implements Issuable.IssueBuilder { + + private final DefaultIssue newIssue; + private final InputComponent primaryComponent; + private TextRange primaryRange = null; + private String primaryMessage = null; + + public DeprecatedIssueBuilderWrapper(InputComponent primaryComponent, DefaultIssue newIssue) { + this.primaryComponent = primaryComponent; + this.newIssue = newIssue; + } + + @Override + public IssueBuilder ruleKey(RuleKey ruleKey) { + newIssue.forRule(ruleKey); + return this; + } + + @Override + public IssueBuilder line(@Nullable Integer line) { + Preconditions.checkState(newIssue.primaryLocation() == null, "Do not use line() and at() for the same issue"); + if (primaryComponent.isFile()) { + if (line != null) { + this.primaryRange = ((InputFile) primaryComponent).selectLine(line.intValue()); + } + return this; + } else { + throw new IllegalArgumentException("Unable to set line for issues on project or directory"); + } + } + + @Override + public IssueBuilder message(String message) { + Preconditions.checkState(newIssue.primaryLocation() == null, "Do not use message() and at() for the same issue"); + this.primaryMessage = message; + return this; + } + + @Override + public NewIssueLocation newLocation() { + return new DefaultIssueLocation(); + } + + @Override + public IssueBuilder at(NewIssueLocation primaryLocation) { + Preconditions.checkState(primaryMessage == null && primaryRange == null, "Do not use message() or line() and at() for the same issue"); + newIssue.at(primaryLocation); + return this; + } + + @Override + public IssueBuilder addLocation(NewIssueLocation secondaryLocation) { + newIssue.addLocation(secondaryLocation); + return this; + } + + @Override + public IssueBuilder addFlow(Iterable<NewIssueLocation> flowLocations) { + newIssue.addFlow(flowLocations); + return this; + } + + @Override + public IssueBuilder severity(String severity) { + newIssue.overrideSeverity(Severity.valueOf(severity)); + return this; + } + + @Override + public IssueBuilder reporter(String reporter) { + throw new UnsupportedOperationException("Not supported during sensor phase"); + } + + @Override + public IssueBuilder effortToFix(Double d) { + newIssue.effortToFix(d); + return this; + } + + @Override + public IssueBuilder attribute(String key, String value) { + throw new UnsupportedOperationException("Not supported during sensor phase"); + } + + @Override + public Issue build() { + if (newIssue.primaryLocation() == null) { + NewIssueLocation newLocation = newIssue.newLocation().on(primaryComponent); + if (primaryMessage != null) { + newLocation.message(primaryMessage); + } + if (primaryComponent.isFile() && primaryRange != null) { + newLocation.at(primaryRange); + } + newIssue.at(newLocation); + } + return new DeprecatedIssueWrapper(newIssue); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DeprecatedIssueFilterChain.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DeprecatedIssueFilterChain.java new file mode 100644 index 00000000000..83994cfb49d --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DeprecatedIssueFilterChain.java @@ -0,0 +1,57 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import com.google.common.collect.ImmutableList; +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.batch.IssueFilter; +import org.sonar.api.issue.batch.IssueFilterChain; + +import java.util.List; + +/** + * @deprecated since 5.3 + */ +@Deprecated +public class DeprecatedIssueFilterChain implements IssueFilterChain { + + private final List<IssueFilter> filters; + + public DeprecatedIssueFilterChain(IssueFilter... filters) { + this.filters = ImmutableList.copyOf(filters); + } + + public DeprecatedIssueFilterChain() { + this.filters = ImmutableList.of(); + } + + private DeprecatedIssueFilterChain(List<IssueFilter> filters) { + this.filters = filters; + } + + @Override + public boolean accept(Issue issue) { + if (filters.isEmpty()) { + return true; + } else { + return filters.get(0).accept(issue, new DeprecatedIssueFilterChain(filters.subList(1, filters.size()))); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DeprecatedIssueWrapper.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DeprecatedIssueWrapper.java new file mode 100644 index 00000000000..61e215cc931 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/DeprecatedIssueWrapper.java @@ -0,0 +1,193 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; +import org.sonar.api.batch.fs.TextRange; +import org.sonar.api.batch.rule.Severity; +import org.sonar.api.batch.sensor.issue.internal.DefaultIssue; +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.IssueComment; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.utils.Duration; + +public class DeprecatedIssueWrapper implements Issue { + + private final DefaultIssue newIssue; + + public DeprecatedIssueWrapper(DefaultIssue newIssue) { + this.newIssue = newIssue; + } + + public DefaultIssue wrapped() { + return newIssue; + } + + @Override + public String key() { + return null; + } + + @Override + public String componentKey() { + return null; + } + + @Override + public RuleKey ruleKey() { + return newIssue.ruleKey(); + } + + @Override + public String language() { + return null; + } + + @Override + public String severity() { + Severity overridenSeverity = newIssue.overriddenSeverity(); + return overridenSeverity != null ? overridenSeverity.name() : null; + } + + @Override + public String message() { + return newIssue.primaryLocation().message(); + } + + @Override + public Integer line() { + TextRange textRange = newIssue.primaryLocation().textRange(); + return textRange != null ? textRange.start().line() : null; + } + + /** + * @deprecated since 5.5, replaced by {@link #gap()} + */ + @Override + @Deprecated + public Double effortToFix() { + return gap(); + } + + @Override + public Double gap() { + return newIssue.effortToFix(); + } + + @Override + public String status() { + return null; + } + + @Override + public String resolution() { + return null; + } + + @Override + public String reporter() { + return null; + } + + @Override + public String assignee() { + return null; + } + + @Override + public Date creationDate() { + return null; + } + + @Override + public Date updateDate() { + return null; + } + + @Override + public Date closeDate() { + return null; + } + + @Override + public String attribute(String key) { + return null; + } + + @Override + public Map<String, String> attributes() { + return Collections.emptyMap(); + } + + @Override + public String authorLogin() { + return null; + } + + @Override + public String actionPlanKey() { + return null; + } + + @Override + public List<IssueComment> comments() { + return Collections.emptyList(); + } + + @Override + public boolean isNew() { + return false; + } + + @Override + public Duration debt() { + return null; + } + + @Override + public Duration effort() { + return null; + } + + @Override + public String projectKey() { + return null; + } + + @Override + public String projectUuid() { + return null; + } + + @Override + public String componentUuid() { + return null; + } + + @Override + public Collection<String> tags() { + return Collections.emptyList(); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/IssuableFactory.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/IssuableFactory.java new file mode 100644 index 00000000000..a2c7afc9c59 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/IssuableFactory.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.issue.Issuable; +import org.sonar.batch.deprecated.perspectives.PerspectiveBuilder; +import org.sonar.batch.index.BatchComponent; +import org.sonar.batch.sensor.DefaultSensorContext; + +/** + * Create the perspective {@link Issuable} on components. + * @since 3.6 + */ +public class IssuableFactory extends PerspectiveBuilder<Issuable> { + + private final SensorContext sensorContext; + + public IssuableFactory(DefaultSensorContext sensorContext) { + super(Issuable.class); + this.sensorContext = sensorContext; + } + + @Override + public Issuable loadPerspective(Class<Issuable> perspectiveClass, BatchComponent component) { + return new DefaultIssuable(component, sensorContext); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/IssueCache.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/IssueCache.java new file mode 100644 index 00000000000..ac1bc6826a5 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/IssueCache.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import org.sonar.batch.issue.tracking.TrackedIssue; + +import org.sonar.api.batch.BatchSide; +import org.sonar.batch.index.Cache; +import org.sonar.batch.index.Caches; + +import java.util.Collection; + +/** + * Shared issues among all project modules + */ +@BatchSide +public class IssueCache { + + // component key -> issue key -> issue + private final Cache<TrackedIssue> cache; + + public IssueCache(Caches caches) { + cache = caches.createCache("issues"); + } + + public Iterable<TrackedIssue> byComponent(String componentKey) { + return cache.values(componentKey); + } + + public Iterable<TrackedIssue> all() { + return cache.values(); + } + + public Collection<Object> componentKeys() { + return cache.keySet(); + } + + public IssueCache put(TrackedIssue issue) { + cache.put(issue.componentKey(), issue.key(), issue); + return this; + } + + public void clear(String componentKey) { + cache.clear(componentKey); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/IssueCallback.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/IssueCallback.java new file mode 100644 index 00000000000..b27b7887045 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/IssueCallback.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +public interface IssueCallback { + void execute(); +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/IssueFilters.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/IssueFilters.java new file mode 100644 index 00000000000..ef542d551ab --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/IssueFilters.java @@ -0,0 +1,69 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import org.sonar.api.scan.issue.filter.FilterableIssue; + +import org.sonar.api.scan.issue.filter.IssueFilterChain; +import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.issue.Issue; +import org.sonar.api.scan.issue.filter.IssueFilter; +import org.sonar.api.resources.Project; + +@BatchSide +public class IssueFilters { + private final IssueFilter[] filters; + private final org.sonar.api.issue.batch.IssueFilter[] deprecatedFilters; + private final Project project; + + public IssueFilters(Project project, IssueFilter[] exclusionFilters, org.sonar.api.issue.batch.IssueFilter[] filters) { + this.project = project; + this.filters = exclusionFilters; + this.deprecatedFilters = filters; + } + + public IssueFilters(Project project, IssueFilter[] filters) { + this(project, filters, new org.sonar.api.issue.batch.IssueFilter[0]); + } + + public IssueFilters(Project project, org.sonar.api.issue.batch.IssueFilter[] deprecatedFilters) { + this(project, new IssueFilter[0], deprecatedFilters); + } + + public IssueFilters(Project project) { + this(project, new IssueFilter[0], new org.sonar.api.issue.batch.IssueFilter[0]); + } + + public boolean accept(String componentKey, ScannerReport.Issue rawIssue) { + IssueFilterChain filterChain = new DefaultIssueFilterChain(filters); + FilterableIssue fIssue = new DefaultFilterableIssue(project, rawIssue, componentKey); + if (filterChain.accept(fIssue)) { + return acceptDeprecated(componentKey, rawIssue); + } + + return false; + } + + public boolean acceptDeprecated(String componentKey, ScannerReport.Issue rawIssue) { + Issue issue = new DeprecatedIssueAdapterForFilter(project, rawIssue, componentKey); + return new DeprecatedIssueFilterChain(deprecatedFilters).accept(issue); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/IssueTransformer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/IssueTransformer.java new file mode 100644 index 00000000000..8a34253114b --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/IssueTransformer.java @@ -0,0 +1,115 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import com.google.common.base.Preconditions; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import javax.annotation.Nullable; +import org.sonar.api.issue.Issue; +import org.sonar.api.rule.RuleKey; +import org.sonar.batch.index.BatchComponent; +import org.sonar.batch.issue.tracking.SourceHashHolder; +import org.sonar.batch.issue.tracking.TrackedIssue; +import org.sonar.core.component.ComponentKeys; +import org.sonar.core.util.Uuids; +import org.sonar.scanner.protocol.input.ScannerInput.ServerIssue; +import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.scanner.protocol.output.ScannerReport.TextRange; + +public class IssueTransformer { + private IssueTransformer() { + // static only + } + + public static TrackedIssue toTrackedIssue(ServerIssue serverIssue) { + TrackedIssue issue = new TrackedIssue(); + issue.setKey(serverIssue.getKey()); + issue.setStatus(serverIssue.getStatus()); + issue.setResolution(serverIssue.hasResolution() ? serverIssue.getResolution() : null); + issue.setMessage(serverIssue.hasMsg() ? serverIssue.getMsg() : null); + issue.setStartLine(serverIssue.hasLine() ? serverIssue.getLine() : null); + issue.setEndLine(serverIssue.hasLine() ? serverIssue.getLine() : null); + issue.setSeverity(serverIssue.getSeverity().name()); + issue.setAssignee(serverIssue.hasAssigneeLogin() ? serverIssue.getAssigneeLogin() : null); + issue.setComponentKey(ComponentKeys.createEffectiveKey(serverIssue.getModuleKey(), serverIssue.hasPath() ? serverIssue.getPath() : null)); + issue.setCreationDate(new Date(serverIssue.getCreationDate())); + issue.setRuleKey(RuleKey.of(serverIssue.getRuleRepository(), serverIssue.getRuleKey())); + issue.setNew(false); + return issue; + } + + public static void close(TrackedIssue issue) { + issue.setStatus(Issue.STATUS_CLOSED); + issue.setStartLine(null); + issue.setEndLine(null); + issue.setResolution(Issue.RESOLUTION_FIXED); + } + + public static void resolveRemove(TrackedIssue issue) { + issue.setStatus(Issue.STATUS_CLOSED); + issue.setStartLine(null); + issue.setEndLine(null); + issue.setResolution(Issue.RESOLUTION_REMOVED); + } + + public static Collection<TrackedIssue> toTrackedIssue(BatchComponent component, Collection<ScannerReport.Issue> rawIssues, @Nullable SourceHashHolder hashes) { + List<TrackedIssue> issues = new ArrayList<>(rawIssues.size()); + + for (ScannerReport.Issue issue : rawIssues) { + issues.add(toTrackedIssue(component, issue, hashes)); + } + + return issues; + } + + public static TrackedIssue toTrackedIssue(BatchComponent component, ScannerReport.Issue rawIssue, @Nullable SourceHashHolder hashes) { + RuleKey ruleKey = RuleKey.of(rawIssue.getRuleRepository(), rawIssue.getRuleKey()); + + Preconditions.checkNotNull(component.key(), "Component key must be set"); + Preconditions.checkNotNull(ruleKey, "Rule key must be set"); + + TrackedIssue issue = new TrackedIssue(hashes != null ? hashes.getHashedSource() : null); + + issue.setKey(Uuids.createFast()); + issue.setComponentKey(component.key()); + issue.setRuleKey(ruleKey); + issue.setGap(rawIssue.hasGap() ? rawIssue.getGap() : null); + issue.setSeverity(rawIssue.getSeverity().name()); + issue.setMessage(rawIssue.hasMsg() ? rawIssue.getMsg() : null); + issue.setResolution(null); + issue.setStatus(Issue.STATUS_OPEN); + issue.setNew(true); + + if (rawIssue.hasTextRange()) { + TextRange r = rawIssue.getTextRange(); + + issue.setStartLine(r.hasStartLine() ? rawIssue.getTextRange().getStartLine() : null); + issue.setStartLineOffset(r.hasStartOffset() ? r.getStartOffset() : null); + issue.setEndLine(r.hasEndLine() ? r.getEndLine() : issue.startLine()); + issue.setEndLineOffset(r.hasEndOffset() ? r.getEndOffset() : null); + } + + return issue; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ModuleIssues.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ModuleIssues.java new file mode 100644 index 00000000000..433c8826f85 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ModuleIssues.java @@ -0,0 +1,155 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import com.google.common.base.Strings; +import org.sonar.api.batch.fs.InputComponent; +import org.sonar.api.batch.fs.TextRange; +import org.sonar.api.batch.rule.ActiveRule; +import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.batch.rule.Rule; +import org.sonar.api.batch.rule.Rules; +import org.sonar.api.batch.sensor.issue.Issue; +import org.sonar.api.batch.sensor.issue.Issue.Flow; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.utils.MessageException; +import org.sonar.batch.index.BatchComponent; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.report.ReportPublisher; +import org.sonar.scanner.protocol.Constants.Severity; +import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.scanner.protocol.output.ScannerReport.IssueLocation; +import org.sonar.scanner.protocol.output.ScannerReport.IssueLocation.Builder; + +/** + * Initialize the issues raised during scan. + */ +public class ModuleIssues { + + private final ActiveRules activeRules; + private final Rules rules; + private final IssueFilters filters; + private final ReportPublisher reportPublisher; + private final BatchComponentCache componentCache; + private final ScannerReport.Issue.Builder builder = ScannerReport.Issue.newBuilder(); + private final Builder locationBuilder = IssueLocation.newBuilder(); + private final org.sonar.scanner.protocol.output.ScannerReport.TextRange.Builder textRangeBuilder = org.sonar.scanner.protocol.output.ScannerReport.TextRange.newBuilder(); + private final ScannerReport.Flow.Builder flowBuilder = ScannerReport.Flow.newBuilder(); + + public ModuleIssues(ActiveRules activeRules, Rules rules, IssueFilters filters, ReportPublisher reportPublisher, BatchComponentCache componentCache) { + this.activeRules = activeRules; + this.rules = rules; + this.filters = filters; + this.reportPublisher = reportPublisher; + this.componentCache = componentCache; + } + + public boolean initAndAddIssue(Issue issue) { + InputComponent inputComponent = issue.primaryLocation().inputComponent(); + BatchComponent component = componentCache.get(inputComponent); + + Rule rule = validateRule(issue); + ActiveRule activeRule = activeRules.find(issue.ruleKey()); + if (activeRule == null) { + // rule does not exist or is not enabled -> ignore the issue + return false; + } + + String primaryMessage = Strings.isNullOrEmpty(issue.primaryLocation().message()) ? rule.name() : issue.primaryLocation().message(); + org.sonar.api.batch.rule.Severity overriddenSeverity = issue.overriddenSeverity(); + Severity severity = overriddenSeverity != null ? Severity.valueOf(overriddenSeverity.name()) : Severity.valueOf(activeRule.severity()); + + builder.clear(); + locationBuilder.clear(); + // non-null fields + builder.setSeverity(severity); + builder.setRuleRepository(issue.ruleKey().repository()); + builder.setRuleKey(issue.ruleKey().rule()); + builder.setMsg(primaryMessage); + locationBuilder.setMsg(primaryMessage); + + locationBuilder.setComponentRef(component.batchId()); + TextRange primaryTextRange = issue.primaryLocation().textRange(); + if (primaryTextRange != null) { + builder.setLine(primaryTextRange.start().line()); + builder.setTextRange(toProtobufTextRange(primaryTextRange)); + } + Double gap = issue.gap(); + if (gap != null) { + builder.setGap(gap); + } + applyFlows(issue); + ScannerReport.Issue rawIssue = builder.build(); + + if (filters.accept(inputComponent.key(), rawIssue)) { + write(component, rawIssue); + return true; + } + return false; + } + + private void applyFlows(Issue issue) { + for (Flow flow : issue.flows()) { + if (!flow.locations().isEmpty()) { + flowBuilder.clear(); + for (org.sonar.api.batch.sensor.issue.IssueLocation location : flow.locations()) { + locationBuilder.clear(); + locationBuilder.setComponentRef(componentCache.get(location.inputComponent()).batchId()); + String message = location.message(); + if (message != null) { + locationBuilder.setMsg(message); + } + TextRange textRange = location.textRange(); + if (textRange != null) { + locationBuilder.setTextRange(toProtobufTextRange(textRange)); + } + flowBuilder.addLocation(locationBuilder.build()); + } + builder.addFlow(flowBuilder.build()); + } + } + } + + private org.sonar.scanner.protocol.output.ScannerReport.TextRange toProtobufTextRange(TextRange primaryTextRange) { + textRangeBuilder.clear(); + textRangeBuilder.setStartLine(primaryTextRange.start().line()); + textRangeBuilder.setStartOffset(primaryTextRange.start().lineOffset()); + textRangeBuilder.setEndLine(primaryTextRange.end().line()); + textRangeBuilder.setEndOffset(primaryTextRange.end().lineOffset()); + return textRangeBuilder.build(); + } + + private Rule validateRule(Issue issue) { + RuleKey ruleKey = issue.ruleKey(); + Rule rule = rules.find(ruleKey); + if (rule == null) { + throw MessageException.of(String.format("The rule '%s' does not exist.", ruleKey)); + } + if (Strings.isNullOrEmpty(rule.name()) && Strings.isNullOrEmpty(issue.primaryLocation().message())) { + throw MessageException.of(String.format("The rule '%s' has no name and the related issue has no message.", ruleKey)); + } + return rule; + } + + public void write(BatchComponent component, ScannerReport.Issue rawIssue) { + reportPublisher.getWriter().appendComponentIssue(component.batchId(), rawIssue); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/TrackedIssueAdapter.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/TrackedIssueAdapter.java new file mode 100644 index 00000000000..19027847d9d --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/TrackedIssueAdapter.java @@ -0,0 +1,202 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.IssueComment; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.utils.Duration; +import org.sonar.batch.issue.tracking.TrackedIssue; + +public class TrackedIssueAdapter implements Issue { + private TrackedIssue issue; + + public TrackedIssueAdapter(TrackedIssue issue) { + this.issue = issue; + } + + @Override + public String key() { + return issue.key(); + } + + @Override + public String componentKey() { + return issue.componentKey(); + } + + @Override + public RuleKey ruleKey() { + return issue.getRuleKey(); + } + + @Override + public String severity() { + return issue.severity(); + } + + @Override + public String message() { + return issue.getMessage(); + } + + @Override + public Integer line() { + return issue.startLine(); + } + + /** + * @deprecated since 5.5, replaced by {@link #gap()} + */ + @Override + @Deprecated + public Double effortToFix() { + return gap(); + } + + @Override + public Double gap() { + return issue.gap(); + } + + @Override + public String status() { + return issue.status(); + } + + @Override + public String resolution() { + return issue.resolution(); + } + + @Override + public String reporter() { + return issue.reporter(); + } + + @Override + public String assignee() { + return issue.assignee(); + } + + @Override + public boolean isNew() { + return issue.isNew(); + } + + @Override + public Map<String, String> attributes() { + return new HashMap<>(); + } + + @Override + public Date creationDate() { + return issue.getCreationDate(); + } + + @Override + public String language() { + return null; + } + + @Override + public Date updateDate() { + return null; + } + + @Override + public Date closeDate() { + return null; + } + + @Override + public String attribute(String key) { + return attributes().get(key); + } + + @Override + public String authorLogin() { + return null; + } + + @Override + public String actionPlanKey() { + return null; + } + + @Override + public List<IssueComment> comments() { + return new ArrayList<>(); + } + + /** + * @deprecated since 5.5, replaced by {@link #effort()} + */ + @Override + @Deprecated + public Duration debt() { + return null; + } + + @Override + public Duration effort() { + return null; + } + + @Override + public String projectKey() { + return null; + } + + @Override + public String projectUuid() { + return null; + } + + @Override + public String componentUuid() { + return null; + } + + @Override + public Collection<String> tags() { + return new ArrayList<>(); + } + + @Override + public boolean equals(Object o) { + if (o == null || !(o instanceof Issue)) { + return false; + } + Issue that = (Issue) o; + return !(issue.key() != null ? !issue.key().equals(that.key()) : (that.key() != null)); + } + + @Override + public int hashCode() { + return issue.key() != null ? issue.key().hashCode() : 0; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/EnforceIssuesFilter.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/EnforceIssuesFilter.java new file mode 100644 index 00000000000..de4920a1f06 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/EnforceIssuesFilter.java @@ -0,0 +1,67 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.ignore; + +import org.sonar.api.scan.issue.filter.FilterableIssue; + +import org.sonar.batch.issue.ignore.pattern.IssueInclusionPatternInitializer; +import org.sonar.batch.issue.ignore.pattern.IssuePattern; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.scan.issue.filter.IssueFilter; +import org.sonar.api.scan.issue.filter.IssueFilterChain; + +public class EnforceIssuesFilter implements IssueFilter { + + private IssueInclusionPatternInitializer patternInitializer; + + private static final Logger LOG = LoggerFactory.getLogger(EnforceIssuesFilter.class); + + public EnforceIssuesFilter(IssueInclusionPatternInitializer patternInitializer) { + this.patternInitializer = patternInitializer; + } + + @Override + public boolean accept(FilterableIssue issue, IssueFilterChain chain) { + boolean atLeastOneRuleMatched = false; + boolean atLeastOnePatternFullyMatched = false; + IssuePattern matchingPattern = null; + + for (IssuePattern pattern : patternInitializer.getMulticriteriaPatterns()) { + if (pattern.getRulePattern().match(issue.ruleKey().toString())) { + atLeastOneRuleMatched = true; + String pathForComponent = patternInitializer.getPathForComponent(issue.componentKey()); + if (pathForComponent != null && pattern.getResourcePattern().match(pathForComponent)) { + atLeastOnePatternFullyMatched = true; + matchingPattern = pattern; + } + } + } + + if (atLeastOneRuleMatched) { + if (atLeastOnePatternFullyMatched) { + LOG.debug("Issue {} enforced by pattern {}", issue, matchingPattern); + } + return atLeastOnePatternFullyMatched; + } else { + return chain.accept(issue); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/IgnoreIssuesFilter.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/IgnoreIssuesFilter.java new file mode 100644 index 00000000000..99a716148c6 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/IgnoreIssuesFilter.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.ignore; + +import org.sonar.api.scan.issue.filter.FilterableIssue; + +import org.sonar.batch.issue.ignore.pattern.IssueExclusionPatternInitializer; +import org.sonar.batch.issue.ignore.pattern.IssuePattern; +import org.sonar.batch.issue.ignore.pattern.PatternMatcher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.scan.issue.filter.IssueFilter; +import org.sonar.api.scan.issue.filter.IssueFilterChain; + +public class IgnoreIssuesFilter implements IssueFilter { + + private PatternMatcher patternMatcher; + + private static final Logger LOG = LoggerFactory.getLogger(IgnoreIssuesFilter.class); + + public IgnoreIssuesFilter(IssueExclusionPatternInitializer patternInitializer) { + this.patternMatcher = patternInitializer.getPatternMatcher(); + } + + @Override + public boolean accept(FilterableIssue issue, IssueFilterChain chain) { + if (hasMatchFor(issue)) { + return false; + } else { + return chain.accept(issue); + } + } + + private boolean hasMatchFor(FilterableIssue issue) { + IssuePattern pattern = patternMatcher.getMatchingPattern(issue); + if (pattern != null) { + logExclusion(issue, pattern); + return true; + } + return false; + } + + private static void logExclusion(FilterableIssue issue, IssuePattern pattern) { + LOG.debug("Issue {} ignored by exclusion pattern {}", issue, pattern); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/package-info.java new file mode 100644 index 00000000000..ead2a62c636 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.issue.ignore; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/pattern/AbstractPatternInitializer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/pattern/AbstractPatternInitializer.java new file mode 100644 index 00000000000..a3e58803a71 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/pattern/AbstractPatternInitializer.java @@ -0,0 +1,81 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.ignore.pattern; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Lists; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.config.Settings; + +import java.util.List; + +import static com.google.common.base.Objects.firstNonNull; + +@BatchSide +public abstract class AbstractPatternInitializer { + + private Settings settings; + + private List<IssuePattern> multicriteriaPatterns; + + protected AbstractPatternInitializer(Settings settings) { + this.settings = settings; + initPatterns(); + } + + protected Settings getSettings() { + return settings; + } + + public List<IssuePattern> getMulticriteriaPatterns() { + return multicriteriaPatterns; + } + + public boolean hasConfiguredPatterns() { + return hasMulticriteriaPatterns(); + } + + public boolean hasMulticriteriaPatterns() { + return !multicriteriaPatterns.isEmpty(); + } + + public abstract void initializePatternsForPath(String relativePath, String componentKey); + + @VisibleForTesting + protected final void initPatterns() { + // Patterns Multicriteria + multicriteriaPatterns = Lists.newArrayList(); + String patternConf = StringUtils.defaultIfBlank(settings.getString(getMulticriteriaConfigurationKey()), ""); + for (String id : StringUtils.split(patternConf, ',')) { + String propPrefix = getMulticriteriaConfigurationKey() + "." + id + "."; + String resourceKeyPattern = settings.getString(propPrefix + "resourceKey"); + String ruleKeyPattern = settings.getString(propPrefix + "ruleKey"); + String lineRange = "*"; + String[] fields = new String[] {resourceKeyPattern, ruleKeyPattern, lineRange}; + PatternDecoder.checkRegularLineConstraints(StringUtils.join(fields, ","), fields); + IssuePattern pattern = new IssuePattern(firstNonNull(resourceKeyPattern, "*"), firstNonNull(ruleKeyPattern, "*")); + PatternDecoder.decodeRangeOfLines(pattern, firstNonNull(lineRange, "*")); + multicriteriaPatterns.add(pattern); + } + } + + protected abstract String getMulticriteriaConfigurationKey(); +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/pattern/IssueExclusionPatternInitializer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/pattern/IssueExclusionPatternInitializer.java new file mode 100644 index 00000000000..74d80f95186 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/pattern/IssueExclusionPatternInitializer.java @@ -0,0 +1,105 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.ignore.pattern; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Lists; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.config.Settings; +import org.sonar.core.config.IssueExclusionProperties; + +import java.util.List; + +import static com.google.common.base.Strings.nullToEmpty; + +public class IssueExclusionPatternInitializer extends AbstractPatternInitializer { + + private List<IssuePattern> blockPatterns; + private List<IssuePattern> allFilePatterns; + private PatternMatcher patternMatcher; + + public IssueExclusionPatternInitializer(Settings settings) { + super(settings); + patternMatcher = new PatternMatcher(); + loadFileContentPatterns(); + } + + @Override + protected String getMulticriteriaConfigurationKey() { + return "sonar.issue.ignore" + ".multicriteria"; + } + + public PatternMatcher getPatternMatcher() { + return patternMatcher; + } + + @Override + public void initializePatternsForPath(String relativePath, String componentKey) { + for (IssuePattern pattern : getMulticriteriaPatterns()) { + if (pattern.matchResource(relativePath)) { + getPatternMatcher().addPatternForComponent(componentKey, pattern); + } + } + } + + @Override + public boolean hasConfiguredPatterns() { + return hasFileContentPattern() || hasMulticriteriaPatterns(); + } + + @VisibleForTesting + protected final void loadFileContentPatterns() { + // Patterns Block + blockPatterns = Lists.newArrayList(); + String patternConf = StringUtils.defaultIfBlank(getSettings().getString(IssueExclusionProperties.PATTERNS_BLOCK_KEY), ""); + for (String id : StringUtils.split(patternConf, ',')) { + String propPrefix = IssueExclusionProperties.PATTERNS_BLOCK_KEY + "." + id + "."; + String beginBlockRegexp = getSettings().getString(propPrefix + IssueExclusionProperties.BEGIN_BLOCK_REGEXP); + String endBlockRegexp = getSettings().getString(propPrefix + IssueExclusionProperties.END_BLOCK_REGEXP); + String[] fields = new String[]{beginBlockRegexp, endBlockRegexp}; + PatternDecoder.checkDoubleRegexpLineConstraints(StringUtils.join(fields, ","), fields); + IssuePattern pattern = new IssuePattern().setBeginBlockRegexp(nullToEmpty(beginBlockRegexp)).setEndBlockRegexp(nullToEmpty(endBlockRegexp)); + blockPatterns.add(pattern); + } + + // Patterns All File + allFilePatterns = Lists.newArrayList(); + patternConf = StringUtils.defaultIfBlank(getSettings().getString(IssueExclusionProperties.PATTERNS_ALLFILE_KEY), ""); + for (String id : StringUtils.split(patternConf, ',')) { + String propPrefix = IssueExclusionProperties.PATTERNS_ALLFILE_KEY + "." + id + "."; + String allFileRegexp = getSettings().getString(propPrefix + IssueExclusionProperties.FILE_REGEXP); + PatternDecoder.checkWholeFileRegexp(allFileRegexp); + IssuePattern pattern = new IssuePattern().setAllFileRegexp(nullToEmpty(allFileRegexp)); + allFilePatterns.add(pattern); + } + } + + public List<IssuePattern> getBlockPatterns() { + return blockPatterns; + } + + public List<IssuePattern> getAllFilePatterns() { + return allFilePatterns; + } + + public boolean hasFileContentPattern() { + return !(blockPatterns.isEmpty() && allFilePatterns.isEmpty()); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/pattern/IssueInclusionPatternInitializer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/pattern/IssueInclusionPatternInitializer.java new file mode 100644 index 00000000000..9fec33bb952 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/pattern/IssueInclusionPatternInitializer.java @@ -0,0 +1,49 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.ignore.pattern; + +import com.google.common.collect.Maps; +import org.sonar.api.config.Settings; + +import java.util.Map; + +public class IssueInclusionPatternInitializer extends AbstractPatternInitializer { + + private Map<String, String> pathForComponent; + + public IssueInclusionPatternInitializer(Settings settings) { + super(settings); + pathForComponent = Maps.newHashMap(); + } + + @Override + protected String getMulticriteriaConfigurationKey() { + return "sonar.issue.enforce" + ".multicriteria"; + } + + @Override + public void initializePatternsForPath(String relativePath, String componentKey) { + pathForComponent.put(componentKey, relativePath); + } + + public String getPathForComponent(String componentKey) { + return pathForComponent.get(componentKey); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/pattern/IssuePattern.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/pattern/IssuePattern.java new file mode 100644 index 00000000000..4c4cc0b4715 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/pattern/IssuePattern.java @@ -0,0 +1,167 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.ignore.pattern; + +import org.sonar.api.scan.issue.filter.FilterableIssue; + +import com.google.common.collect.Sets; +import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.utils.WildcardPattern; + +import java.util.Set; + +public class IssuePattern { + + private WildcardPattern resourcePattern; + private WildcardPattern rulePattern; + private Set<Integer> lines = Sets.newLinkedHashSet(); + private Set<LineRange> lineRanges = Sets.newLinkedHashSet(); + private String beginBlockRegexp; + private String endBlockRegexp; + private String allFileRegexp; + private boolean checkLines = true; + + public IssuePattern() { + } + + public IssuePattern(String resourcePattern, String rulePattern) { + this.resourcePattern = WildcardPattern.create(resourcePattern); + this.rulePattern = WildcardPattern.create(rulePattern); + } + + public IssuePattern(String resourcePattern, String rulePattern, Set<LineRange> lineRanges) { + this(resourcePattern, rulePattern); + this.lineRanges = lineRanges; + } + + public WildcardPattern getResourcePattern() { + return resourcePattern; + } + + public WildcardPattern getRulePattern() { + return rulePattern; + } + + public String getBeginBlockRegexp() { + return beginBlockRegexp; + } + + public String getEndBlockRegexp() { + return endBlockRegexp; + } + + public String getAllFileRegexp() { + return allFileRegexp; + } + + IssuePattern addLineRange(int fromLineId, int toLineId) { + lineRanges.add(new LineRange(fromLineId, toLineId)); + return this; + } + + IssuePattern addLine(int lineId) { + lines.add(lineId); + return this; + } + + boolean isCheckLines() { + return checkLines; + } + + IssuePattern setCheckLines(boolean b) { + this.checkLines = b; + return this; + } + + IssuePattern setBeginBlockRegexp(String beginBlockRegexp) { + this.beginBlockRegexp = beginBlockRegexp; + return this; + } + + IssuePattern setEndBlockRegexp(String endBlockRegexp) { + this.endBlockRegexp = endBlockRegexp; + return this; + } + + IssuePattern setAllFileRegexp(String allFileRegexp) { + this.allFileRegexp = allFileRegexp; + return this; + } + + Set<Integer> getAllLines() { + Set<Integer> allLines = Sets.newLinkedHashSet(lines); + for (LineRange lineRange : lineRanges) { + allLines.addAll(lineRange.toLines()); + } + return allLines; + } + + public boolean match(FilterableIssue issue) { + boolean match = matchResource(issue.componentKey()) + && matchRule(issue.ruleKey()); + if (checkLines) { + Integer line = issue.line(); + if (line == null) { + match = false; + } else { + match = match && matchLine(line); + } + } + return match; + } + + boolean matchLine(int lineId) { + if (lines.contains(lineId)) { + return true; + } + + for (LineRange range : lineRanges) { + if (range.in(lineId)) { + return true; + } + } + + return false; + } + + boolean matchRule(RuleKey rule) { + if (rule == null) { + return false; + } + + String key = new StringBuilder().append(rule.repository()).append(':').append(rule.rule()).toString(); + return rulePattern.match(key); + } + + boolean matchResource(String resource) { + return resource != null && resourcePattern.match(resource); + } + + public IssuePattern forResource(String resource) { + return new IssuePattern(resource, rulePattern.toString(), lineRanges).setCheckLines(isCheckLines()); + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/pattern/LineRange.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/pattern/LineRange.java new file mode 100644 index 00000000000..72d43f5f216 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/pattern/LineRange.java @@ -0,0 +1,90 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.ignore.pattern; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Sets; + +import java.util.Set; + +public class LineRange { + private int from; + private int to; + + public LineRange(int from, int to) { + Preconditions.checkArgument(from <= to, "Line range is not valid: %s must be greater than %s", from, to); + + this.from = from; + this.to = to; + } + + public boolean in(int lineId) { + return from <= lineId && lineId <= to; + } + + public Set<Integer> toLines() { + Set<Integer> lines = Sets.newLinkedHashSet(); + for (int index = from; index <= to; index++) { + lines.add(index); + } + return lines; + } + + @Override + public String toString() { + return "[" + from + "-" + to + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + from; + result = prime * result + to; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + if (fieldsDiffer((LineRange) obj)) { + return false; + } + return true; + } + + private boolean fieldsDiffer(LineRange other) { + if (from != other.from) { + return true; + } + if (to != other.to) { + return true; + } + return false; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/pattern/PatternDecoder.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/pattern/PatternDecoder.java new file mode 100644 index 00000000000..11dc501cac4 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/pattern/PatternDecoder.java @@ -0,0 +1,143 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.ignore.pattern; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Lists; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.utils.SonarException; + +import java.util.List; + +public class PatternDecoder { + + private static final int THREE_FIELDS_PER_LINE = 3; + private static final String LINE_RANGE_REGEXP = "\\[((\\d+|\\d+-\\d+),?)*\\]"; + private static final String CONFIG_FORMAT_ERROR_PREFIX = "Exclusions > Issues : Invalid format. "; + + public List<IssuePattern> decode(String patternsList) { + List<IssuePattern> patterns = Lists.newLinkedList(); + String[] patternsLines = StringUtils.split(patternsList, "\n"); + for (String patternLine : patternsLines) { + IssuePattern pattern = decodeLine(patternLine.trim()); + if (pattern != null) { + patterns.add(pattern); + } + } + return patterns; + } + + /** + * Main method that decodes a line which defines a pattern + */ + public IssuePattern decodeLine(String line) { + if (isBlankOrComment(line)) { + return null; + } + + String[] fields = StringUtils.splitPreserveAllTokens(line, ';'); + if (fields.length > THREE_FIELDS_PER_LINE) { + throw new SonarException(CONFIG_FORMAT_ERROR_PREFIX + "The following line has more than 3 fields separated by comma: " + line); + } + + IssuePattern pattern; + if (fields.length == THREE_FIELDS_PER_LINE) { + checkRegularLineConstraints(line, fields); + pattern = new IssuePattern(StringUtils.trim(fields[0]), StringUtils.trim(fields[1])); + decodeRangeOfLines(pattern, fields[2]); + } else if (fields.length == 2) { + checkDoubleRegexpLineConstraints(line, fields); + pattern = new IssuePattern().setBeginBlockRegexp(fields[0]).setEndBlockRegexp(fields[1]); + } else { + checkWholeFileRegexp(fields[0]); + pattern = new IssuePattern().setAllFileRegexp(fields[0]); + } + + return pattern; + } + + static void checkRegularLineConstraints(String line, String[] fields) { + if (!isResource(fields[0])) { + throw new SonarException(CONFIG_FORMAT_ERROR_PREFIX + "The first field does not define a resource pattern: " + line); + } + if (!isRule(fields[1])) { + throw new SonarException(CONFIG_FORMAT_ERROR_PREFIX + "The second field does not define a rule pattern: " + line); + } + if (!isLinesRange(fields[2])) { + throw new SonarException(CONFIG_FORMAT_ERROR_PREFIX + "The third field does not define a range of lines: " + line); + } + } + + static void checkDoubleRegexpLineConstraints(String line, String[] fields) { + if (!isRegexp(fields[0])) { + throw new SonarException(CONFIG_FORMAT_ERROR_PREFIX + "The first field does not define a regular expression: " + line); + } + // As per configuration help, missing second field means: from start regexp to EOF + } + + static void checkWholeFileRegexp(String regexp) { + if (!isRegexp(regexp)) { + throw new SonarException(CONFIG_FORMAT_ERROR_PREFIX + "The field does not define a regular expression: " + regexp); + } + } + + public static void decodeRangeOfLines(IssuePattern pattern, String field) { + if (StringUtils.equals(field, "*")) { + pattern.setCheckLines(false); + } else { + pattern.setCheckLines(true); + String s = StringUtils.substringBetween(StringUtils.trim(field), "[", "]"); + String[] parts = StringUtils.split(s, ','); + for (String part : parts) { + if (StringUtils.contains(part, '-')) { + String[] range = StringUtils.split(part, '-'); + pattern.addLineRange(Integer.valueOf(range[0]), Integer.valueOf(range[1])); + } else { + pattern.addLine(Integer.valueOf(part)); + } + } + } + } + + @VisibleForTesting + static boolean isLinesRange(String field) { + return StringUtils.equals(field, "*") || java.util.regex.Pattern.matches(LINE_RANGE_REGEXP, field); + } + + @VisibleForTesting + static boolean isBlankOrComment(String line) { + return StringUtils.isBlank(line) ^ StringUtils.startsWith(line, "#"); + } + + @VisibleForTesting + static boolean isResource(String field) { + return StringUtils.isNotBlank(field); + } + + @VisibleForTesting + static boolean isRule(String field) { + return StringUtils.isNotBlank(field); + } + + @VisibleForTesting + static boolean isRegexp(String field) { + return StringUtils.isNotBlank(field); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/pattern/PatternMatcher.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/pattern/PatternMatcher.java new file mode 100644 index 00000000000..80b6f716cff --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/pattern/PatternMatcher.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.ignore.pattern; + +import org.sonar.api.scan.issue.filter.FilterableIssue; + +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.Multimap; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +public class PatternMatcher { + + private Multimap<String, IssuePattern> patternByComponent = LinkedHashMultimap.create(); + + public IssuePattern getMatchingPattern(FilterableIssue issue) { + IssuePattern matchingPattern = null; + Iterator<IssuePattern> patternIterator = getPatternsForComponent(issue.componentKey()).iterator(); + while(matchingPattern == null && patternIterator.hasNext()) { + IssuePattern nextPattern = patternIterator.next(); + if (nextPattern.match(issue)) { + matchingPattern = nextPattern; + } + } + return matchingPattern; + } + + public Collection<IssuePattern> getPatternsForComponent(String componentKey) { + return patternByComponent.get(componentKey); + } + + public void addPatternForComponent(String component, IssuePattern pattern) { + patternByComponent.put(component, pattern.forResource(component)); + } + + public void addPatternToExcludeResource(String resource) { + addPatternForComponent(resource, new IssuePattern(resource, "*").setCheckLines(false)); + } + + public void addPatternToExcludeLines(String resource, Set<LineRange> lineRanges) { + addPatternForComponent(resource, new IssuePattern(resource, "*", lineRanges).setCheckLines(true)); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/pattern/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/pattern/package-info.java new file mode 100644 index 00000000000..ebbc7b8371e --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/pattern/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.issue.ignore.pattern; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/scanner/IssueExclusionsLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/scanner/IssueExclusionsLoader.java new file mode 100644 index 00000000000..8622dc618eb --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/scanner/IssueExclusionsLoader.java @@ -0,0 +1,81 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.ignore.scanner; + +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.resources.Project; +import org.sonar.batch.issue.ignore.pattern.IssueExclusionPatternInitializer; +import org.sonar.batch.issue.ignore.pattern.IssueInclusionPatternInitializer; + +import java.nio.charset.Charset; + +public final class IssueExclusionsLoader { + + private final IssueExclusionsRegexpScanner regexpScanner; + private final IssueExclusionPatternInitializer exclusionPatternInitializer; + private final IssueInclusionPatternInitializer inclusionPatternInitializer; + private final FileSystem fileSystem; + + public IssueExclusionsLoader(IssueExclusionsRegexpScanner regexpScanner, IssueExclusionPatternInitializer exclusionPatternInitializer, + IssueInclusionPatternInitializer inclusionPatternInitializer, + FileSystem fileSystem) { + this.regexpScanner = regexpScanner; + this.exclusionPatternInitializer = exclusionPatternInitializer; + this.inclusionPatternInitializer = inclusionPatternInitializer; + this.fileSystem = fileSystem; + } + + public boolean shouldExecuteOnProject(Project project) { + return inclusionPatternInitializer.hasConfiguredPatterns() + || exclusionPatternInitializer.hasConfiguredPatterns(); + } + + /** + * {@inheritDoc} + */ + public void execute() { + Charset sourcesEncoding = fileSystem.encoding(); + + for (InputFile inputFile : fileSystem.inputFiles(fileSystem.predicates().all())) { + try { + String componentEffectiveKey = ((DefaultInputFile) inputFile).key(); + if (componentEffectiveKey != null) { + String path = inputFile.relativePath(); + inclusionPatternInitializer.initializePatternsForPath(path, componentEffectiveKey); + exclusionPatternInitializer.initializePatternsForPath(path, componentEffectiveKey); + if (exclusionPatternInitializer.hasFileContentPattern()) { + regexpScanner.scan(componentEffectiveKey, inputFile.file(), sourcesEncoding); + } + } + } catch (Exception e) { + throw new IllegalStateException("Unable to read the source file : '" + inputFile.absolutePath() + "' with the charset : '" + + sourcesEncoding.name() + "'.", e); + } + } + } + + @Override + public String toString() { + return "Issues Exclusions - Source Scanner"; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScanner.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScanner.java new file mode 100644 index 00000000000..de465ad4dc5 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScanner.java @@ -0,0 +1,198 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.ignore.scanner; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.BatchSide; +import org.sonar.batch.issue.ignore.pattern.IssueExclusionPatternInitializer; +import org.sonar.batch.issue.ignore.pattern.IssuePattern; +import org.sonar.batch.issue.ignore.pattern.LineRange; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Set; + +@BatchSide +public class IssueExclusionsRegexpScanner { + + private static final Logger LOG = LoggerFactory.getLogger(IssueExclusionsRegexpScanner.class); + + private IssueExclusionPatternInitializer exclusionPatternInitializer; + private List<java.util.regex.Pattern> allFilePatterns; + private List<DoubleRegexpMatcher> blockMatchers; + + // fields to be reset at every new scan + private DoubleRegexpMatcher currentMatcher; + private int fileLength; + private List<LineExclusion> lineExclusions; + private LineExclusion currentLineExclusion; + + public IssueExclusionsRegexpScanner(IssueExclusionPatternInitializer patternsInitializer) { + this.exclusionPatternInitializer = patternsInitializer; + + lineExclusions = Lists.newArrayList(); + allFilePatterns = Lists.newArrayList(); + blockMatchers = Lists.newArrayList(); + + for (IssuePattern pattern : patternsInitializer.getAllFilePatterns()) { + allFilePatterns.add(java.util.regex.Pattern.compile(pattern.getAllFileRegexp())); + } + for (IssuePattern pattern : patternsInitializer.getBlockPatterns()) { + blockMatchers.add(new DoubleRegexpMatcher( + java.util.regex.Pattern.compile(pattern.getBeginBlockRegexp()), + java.util.regex.Pattern.compile(pattern.getEndBlockRegexp()))); + } + + init(); + } + + private void init() { + currentMatcher = null; + fileLength = 0; + lineExclusions.clear(); + currentLineExclusion = null; + } + + public void scan(String resource, File file, Charset sourcesEncoding) throws IOException { + LOG.debug("Scanning {}", resource); + init(); + + List<String> lines = FileUtils.readLines(file, sourcesEncoding.name()); + int lineIndex = 0; + for (String line : lines) { + lineIndex++; + if (line.trim().length() == 0) { + continue; + } + + // first check the single regexp patterns that can be used to totally exclude a file + for (java.util.regex.Pattern pattern : allFilePatterns) { + if (pattern.matcher(line).find()) { + exclusionPatternInitializer.getPatternMatcher().addPatternToExcludeResource(resource); + // nothing more to do on this file + LOG.debug("- Exclusion pattern '{}': every violation in this file will be ignored.", pattern); + return; + } + } + + // then check the double regexps if we're still here + checkDoubleRegexps(line, lineIndex); + } + + if (currentMatcher != null && !currentMatcher.hasSecondPattern()) { + // this will happen when there is a start block regexp but no end block regexp + endExclusion(lineIndex + 1); + } + + // now create the new line-based pattern for this file if there are exclusions + fileLength = lineIndex; + if (!lineExclusions.isEmpty()) { + Set<LineRange> lineRanges = convertLineExclusionsToLineRanges(); + LOG.debug("- Line exclusions found: {}", lineRanges); + exclusionPatternInitializer.getPatternMatcher().addPatternToExcludeLines(resource, lineRanges); + } + } + + private Set<LineRange> convertLineExclusionsToLineRanges() { + Set<LineRange> lineRanges = Sets.newHashSet(); + for (LineExclusion lineExclusion : lineExclusions) { + lineRanges.add(lineExclusion.toLineRange()); + } + return lineRanges; + } + + private void checkDoubleRegexps(String line, int lineIndex) { + if (currentMatcher == null) { + for (DoubleRegexpMatcher matcher : blockMatchers) { + if (matcher.matchesFirstPattern(line)) { + startExclusion(lineIndex); + currentMatcher = matcher; + break; + } + } + } else { + if (currentMatcher.matchesSecondPattern(line)) { + endExclusion(lineIndex); + currentMatcher = null; + } + } + } + + private void startExclusion(int lineIndex) { + currentLineExclusion = new LineExclusion(lineIndex); + lineExclusions.add(currentLineExclusion); + } + + private void endExclusion(int lineIndex) { + currentLineExclusion.setEnd(lineIndex); + currentLineExclusion = null; + } + + private class LineExclusion { + + private int start; + private int end; + + LineExclusion(int start) { + this.start = start; + this.end = -1; + } + + void setEnd(int end) { + this.end = end; + } + + public LineRange toLineRange() { + return new LineRange(start, end == -1 ? fileLength : end); + } + + } + + private static class DoubleRegexpMatcher { + + private java.util.regex.Pattern firstPattern; + private java.util.regex.Pattern secondPattern; + + DoubleRegexpMatcher(java.util.regex.Pattern firstPattern, java.util.regex.Pattern secondPattern) { + this.firstPattern = firstPattern; + this.secondPattern = secondPattern; + } + + boolean matchesFirstPattern(String line) { + return firstPattern.matcher(line).find(); + } + + boolean matchesSecondPattern(String line) { + return hasSecondPattern() && secondPattern.matcher(line).find(); + } + + boolean hasSecondPattern() { + return StringUtils.isNotEmpty(secondPattern.toString()); + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/scanner/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/scanner/package-info.java new file mode 100644 index 00000000000..3beb574f061 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/ignore/scanner/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.issue.ignore.scanner; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/package-info.java new file mode 100644 index 00000000000..75db5554d7f --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.issue; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/DefaultServerLineHashesLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/DefaultServerLineHashesLoader.java new file mode 100644 index 00000000000..954997466fe --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/DefaultServerLineHashesLoader.java @@ -0,0 +1,68 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.tracking; + +import org.sonar.batch.cache.WSLoader.LoadStrategy; + +import org.sonar.batch.cache.WSLoaderResult; +import org.sonar.batch.cache.WSLoader; +import org.apache.commons.lang.mutable.MutableBoolean; + +import javax.annotation.Nullable; + +import org.sonar.batch.util.BatchUtils; +import com.google.common.base.Splitter; +import com.google.common.collect.Iterators; +import org.sonar.api.utils.log.Loggers; +import org.sonar.api.utils.log.Profiler; + +public class DefaultServerLineHashesLoader implements ServerLineHashesLoader { + + private final WSLoader wsLoader; + + public DefaultServerLineHashesLoader(WSLoader wsLoader) { + this.wsLoader = wsLoader; + } + + @Override + public String[] getLineHashes(String fileKey, @Nullable MutableBoolean fromCache) { + String hashesFromWs = loadHashesFromWs(fileKey, fromCache); + return Iterators.toArray(Splitter.on('\n').split(hashesFromWs).iterator(), String.class); + } + + private String loadHashesFromWs(String fileKey, @Nullable MutableBoolean fromCache) { + Profiler profiler = Profiler.createIfDebug(Loggers.get(getClass())) + .addContext("file", fileKey) + .startDebug("Load line hashes"); + WSLoaderResult<String> result = wsLoader.loadString("/api/sources/hash?key=" + BatchUtils.encodeForUrl(fileKey), LoadStrategy.CACHE_FIRST); + try { + if (fromCache != null) { + fromCache.setValue(result.isFromCache()); + } + return result.get(); + } finally { + if (result.isFromCache()) { + profiler.stopDebug("Load line hashes (done from cache)"); + } else { + profiler.stopDebug(); + } + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/FileHashes.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/FileHashes.java new file mode 100644 index 00000000000..3284b5a681b --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/FileHashes.java @@ -0,0 +1,96 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.tracking; + +import org.sonar.api.batch.fs.internal.FileMetadata; +import org.sonar.api.batch.fs.internal.FileMetadata.LineHashConsumer; + +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.Multimap; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.lang.ObjectUtils; +import org.sonar.api.batch.fs.internal.DefaultInputFile; + +import javax.annotation.Nullable; + +import java.util.Collection; + +/** + * Wraps a {@link Sequence} to assign hash codes to elements. + */ +public final class FileHashes { + + private final String[] hashes; + private final Multimap<String, Integer> linesByHash; + + private FileHashes(String[] hashes, Multimap<String, Integer> linesByHash) { + this.hashes = hashes; + this.linesByHash = linesByHash; + } + + public static FileHashes create(String[] hashes) { + int size = hashes.length; + Multimap<String, Integer> linesByHash = LinkedHashMultimap.create(); + for (int i = 0; i < size; i++) { + // indices in array are shifted one line before + linesByHash.put(hashes[i], i + 1); + } + return new FileHashes(hashes, linesByHash); + } + + public static FileHashes create(DefaultInputFile f) { + final byte[][] hashes = new byte[f.lines()][]; + FileMetadata.computeLineHashesForIssueTracking(f, new LineHashConsumer() { + + @Override + public void consume(int lineIdx, @Nullable byte[] hash) { + hashes[lineIdx - 1] = hash; + } + }); + + int size = hashes.length; + Multimap<String, Integer> linesByHash = LinkedHashMultimap.create(); + String[] hexHashes = new String[size]; + for (int i = 0; i < size; i++) { + String hash = hashes[i] != null ? Hex.encodeHexString(hashes[i]) : ""; + hexHashes[i] = hash; + // indices in array are shifted one line before + linesByHash.put(hash, i + 1); + } + return new FileHashes(hexHashes, linesByHash); + } + + public int length() { + return hashes.length; + } + + public Collection<Integer> getLinesForHash(String hash) { + return linesByHash.get(hash); + } + + public String[] hashes() { + return hashes; + } + + public String getHash(int line) { + // indices in array are shifted one line before + return (String) ObjectUtils.defaultIfNull(hashes[line - 1], ""); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/IssueTrackingInput.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/IssueTrackingInput.java new file mode 100644 index 00000000000..031396b54d7 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/IssueTrackingInput.java @@ -0,0 +1,58 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.tracking; + +import org.sonar.core.issue.tracking.Trackable; +import org.sonar.core.issue.tracking.BlockHashSequence; +import org.sonar.core.issue.tracking.LineHashSequence; + +import java.util.Collection; +import java.util.List; + +import org.sonar.core.issue.tracking.Input; + +public class IssueTrackingInput<T extends Trackable> implements Input<T> { + + private final Collection<T> issues; + private final LineHashSequence lineHashes; + private final BlockHashSequence blockHashes; + + public IssueTrackingInput(Collection<T> issues, List<String> hashes) { + this.issues = issues; + this.lineHashes = new LineHashSequence(hashes); + this.blockHashes = BlockHashSequence.create(lineHashes); + } + + @Override + public LineHashSequence getLineHashSequence() { + return lineHashes; + } + + @Override + public BlockHashSequence getBlockHashSequence() { + return blockHashes; + } + + @Override + public Collection<T> getIssues() { + return issues; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/IssueTransition.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/IssueTransition.java new file mode 100644 index 00000000000..bff1974a99d --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/IssueTransition.java @@ -0,0 +1,123 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.tracking; + +import org.sonar.batch.util.ProgressReport; +import org.sonar.batch.issue.IssueTransformer; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.resources.Project; +import org.sonar.batch.index.BatchComponent; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.issue.IssueCache; +import org.sonar.batch.report.ReportPublisher; +import org.sonar.core.util.CloseableIterator; +import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.scanner.protocol.output.ScannerReportReader; +import javax.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +@BatchSide +public class IssueTransition { + private final IssueCache issueCache; + private final BatchComponentCache componentCache; + private final ReportPublisher reportPublisher; + private final Date analysisDate; + @Nullable + private final LocalIssueTracking localIssueTracking; + + public IssueTransition(BatchComponentCache componentCache, IssueCache issueCache, ReportPublisher reportPublisher, + @Nullable LocalIssueTracking localIssueTracking) { + this.componentCache = componentCache; + this.issueCache = issueCache; + this.reportPublisher = reportPublisher; + this.localIssueTracking = localIssueTracking; + this.analysisDate = ((Project) componentCache.getRoot().resource()).getAnalysisDate(); + } + + public IssueTransition(BatchComponentCache componentCache, IssueCache issueCache, ReportPublisher reportPublisher) { + this(componentCache, issueCache, reportPublisher, null); + } + + public void execute() { + if (localIssueTracking != null) { + localIssueTracking.init(); + } + + ScannerReportReader reader = new ScannerReportReader(reportPublisher.getReportDir()); + int nbComponents = componentCache.all().size(); + + if (nbComponents == 0) { + return; + } + + ProgressReport progressReport = new ProgressReport("issue-tracking-report", TimeUnit.SECONDS.toMillis(10)); + progressReport.start("Performing issue tracking"); + int count = 0; + + try { + for (BatchComponent component : componentCache.all()) { + trackIssues(reader, component); + count++; + progressReport.message(count + "/" + nbComponents + " components tracked"); + } + } finally { + progressReport.stop(count + "/" + nbComponents + " components tracked"); + } + } + + public void trackIssues(ScannerReportReader reader, BatchComponent component) { + // raw issues = all the issues created by rule engines during this module scan and not excluded by filters + List<ScannerReport.Issue> rawIssues = new LinkedList<>(); + try (CloseableIterator<ScannerReport.Issue> it = reader.readComponentIssues(component.batchId())) { + while (it.hasNext()) { + rawIssues.add(it.next()); + } + } catch (Exception e) { + throw new IllegalStateException("Can't read issues for " + component.key(), e); + } + + List<TrackedIssue> trackedIssues; + if (localIssueTracking != null) { + trackedIssues = localIssueTracking.trackIssues(component, rawIssues, analysisDate); + } else { + trackedIssues = doTransition(rawIssues, component); + } + + for (TrackedIssue issue : trackedIssues) { + issueCache.put(issue); + } + } + + private static List<TrackedIssue> doTransition(List<ScannerReport.Issue> rawIssues, BatchComponent component) { + List<TrackedIssue> issues = new ArrayList<>(rawIssues.size()); + + for (ScannerReport.Issue issue : rawIssues) { + issues.add(IssueTransformer.toTrackedIssue(component, issue, null)); + } + + return issues; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/LocalIssueTracking.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/LocalIssueTracking.java new file mode 100644 index 00000000000..634326771d4 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/LocalIssueTracking.java @@ -0,0 +1,266 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.tracking; + +import org.sonar.core.issue.tracking.Tracking; +import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.core.issue.tracking.Input; +import org.sonar.core.issue.tracking.Tracker; +import org.sonar.batch.issue.IssueTransformer; +import org.sonar.api.batch.fs.InputFile.Status; +import org.sonar.batch.analysis.DefaultAnalysisMode; +import com.google.common.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.rule.ActiveRule; +import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.issue.Issue; +import org.sonar.api.resources.ResourceUtils; +import org.sonar.batch.index.BatchComponent; +import org.sonar.batch.repository.ProjectRepositories; + +@BatchSide +public class LocalIssueTracking { + private final Tracker<TrackedIssue, ServerIssueFromWs> tracker; + private final ServerLineHashesLoader lastLineHashes; + private final ActiveRules activeRules; + private final ServerIssueRepository serverIssueRepository; + private final DefaultAnalysisMode mode; + + private boolean hasServerAnalysis; + + public LocalIssueTracking(Tracker<TrackedIssue, ServerIssueFromWs> tracker, ServerLineHashesLoader lastLineHashes, + ActiveRules activeRules, ServerIssueRepository serverIssueRepository, ProjectRepositories projectRepositories, DefaultAnalysisMode mode) { + this.tracker = tracker; + this.lastLineHashes = lastLineHashes; + this.serverIssueRepository = serverIssueRepository; + this.mode = mode; + this.activeRules = activeRules; + this.hasServerAnalysis = projectRepositories.lastAnalysisDate() != null; + } + + public void init() { + if (hasServerAnalysis) { + serverIssueRepository.load(); + } + } + + public List<TrackedIssue> trackIssues(BatchComponent component, Collection<ScannerReport.Issue> reportIssues, Date analysisDate) { + List<TrackedIssue> trackedIssues = new LinkedList<>(); + if (hasServerAnalysis) { + // all the issues that are not closed in db before starting this module scan, including manual issues + Collection<ServerIssueFromWs> serverIssues = loadServerIssues(component); + + if (shouldCopyServerIssues(component)) { + // raw issues should be empty, we just need to deal with server issues (SONAR-6931) + copyServerIssues(serverIssues, trackedIssues); + } else { + + SourceHashHolder sourceHashHolder = loadSourceHashes(component); + Collection<TrackedIssue> rIssues = IssueTransformer.toTrackedIssue(component, reportIssues, sourceHashHolder); + + Input<ServerIssueFromWs> baseIssues = createBaseInput(serverIssues, sourceHashHolder); + Input<TrackedIssue> rawIssues = createRawInput(rIssues, sourceHashHolder); + + Tracking<TrackedIssue, ServerIssueFromWs> track = tracker.track(rawIssues, baseIssues); + + addUnmatchedFromServer(track.getUnmatchedBases(), sourceHashHolder, trackedIssues); + addUnmatchedFromServer(track.getOpenManualIssuesByLine().values(), sourceHashHolder, trackedIssues); + mergeMatched(track, trackedIssues, rIssues); + addUnmatchedFromReport(track.getUnmatchedRaws(), trackedIssues, analysisDate); + } + } + + if (hasServerAnalysis && ResourceUtils.isRootProject(component.resource())) { + // issues that relate to deleted components + addIssuesOnDeletedComponents(trackedIssues); + } + + return trackedIssues; + } + + private static Input<ServerIssueFromWs> createBaseInput(Collection<ServerIssueFromWs> serverIssues, @Nullable SourceHashHolder sourceHashHolder) { + List<String> refHashes; + + if (sourceHashHolder != null && sourceHashHolder.getHashedReference() != null) { + refHashes = Arrays.asList(sourceHashHolder.getHashedReference().hashes()); + } else { + refHashes = new ArrayList<>(0); + } + + return new IssueTrackingInput<>(serverIssues, refHashes); + } + + private static Input<TrackedIssue> createRawInput(Collection<TrackedIssue> rIssues, @Nullable SourceHashHolder sourceHashHolder) { + List<String> baseHashes; + if (sourceHashHolder != null && sourceHashHolder.getHashedSource() != null) { + baseHashes = Arrays.asList(sourceHashHolder.getHashedSource().hashes()); + } else { + baseHashes = new ArrayList<>(0); + } + + return new IssueTrackingInput<>(rIssues, baseHashes); + } + + private boolean shouldCopyServerIssues(BatchComponent component) { + if (!mode.scanAllFiles() && component.isFile()) { + DefaultInputFile inputFile = (DefaultInputFile) component.inputComponent(); + if (inputFile.status() == Status.SAME) { + return true; + } + } + return false; + } + + private void copyServerIssues(Collection<ServerIssueFromWs> serverIssues, List<TrackedIssue> trackedIssues) { + for (ServerIssueFromWs serverIssue : serverIssues) { + org.sonar.scanner.protocol.input.ScannerInput.ServerIssue unmatchedPreviousIssue = serverIssue.getDto(); + TrackedIssue unmatched = IssueTransformer.toTrackedIssue(unmatchedPreviousIssue); + + ActiveRule activeRule = activeRules.find(unmatched.getRuleKey()); + unmatched.setNew(false); + + if (activeRule == null) { + // rule removed + IssueTransformer.resolveRemove(unmatched); + } + + trackedIssues.add(unmatched); + } + } + + @CheckForNull + private SourceHashHolder loadSourceHashes(BatchComponent component) { + SourceHashHolder sourceHashHolder = null; + if (component.isFile()) { + DefaultInputFile file = (DefaultInputFile) component.inputComponent(); + if (file == null) { + throw new IllegalStateException("Resource " + component.resource() + " was not found in InputPath cache"); + } + sourceHashHolder = new SourceHashHolder(file, lastLineHashes); + } + return sourceHashHolder; + } + + private Collection<ServerIssueFromWs> loadServerIssues(BatchComponent component) { + Collection<ServerIssueFromWs> serverIssues = new ArrayList<>(); + for (org.sonar.scanner.protocol.input.ScannerInput.ServerIssue previousIssue : serverIssueRepository.byComponent(component)) { + serverIssues.add(new ServerIssueFromWs(previousIssue)); + } + return serverIssues; + } + + @VisibleForTesting + protected void mergeMatched(Tracking<TrackedIssue, ServerIssueFromWs> result, Collection<TrackedIssue> mergeTo, Collection<TrackedIssue> rawIssues) { + for (Map.Entry<TrackedIssue, ServerIssueFromWs> e : result.getMatchedRaws().entrySet()) { + org.sonar.scanner.protocol.input.ScannerInput.ServerIssue dto = e.getValue().getDto(); + TrackedIssue tracked = e.getKey(); + + // invariant fields + tracked.setKey(dto.getKey()); + + // non-persisted fields + tracked.setNew(false); + + // fields to update with old values + tracked.setResolution(dto.hasResolution() ? dto.getResolution() : null); + tracked.setStatus(dto.getStatus()); + tracked.setAssignee(dto.hasAssigneeLogin() ? dto.getAssigneeLogin() : null); + tracked.setCreationDate(new Date(dto.getCreationDate())); + + if (dto.getManualSeverity()) { + // Severity overriden by user + tracked.setSeverity(dto.getSeverity().name()); + } + mergeTo.add(tracked); + } + } + + private void addUnmatchedFromServer(Iterable<ServerIssueFromWs> unmatchedIssues, SourceHashHolder sourceHashHolder, Collection<TrackedIssue> mergeTo) { + for (ServerIssueFromWs unmatchedIssue : unmatchedIssues) { + org.sonar.scanner.protocol.input.ScannerInput.ServerIssue unmatchedPreviousIssue = unmatchedIssue.getDto(); + TrackedIssue unmatched = IssueTransformer.toTrackedIssue(unmatchedPreviousIssue); + if (unmatchedIssue.getRuleKey().isManual() && !Issue.STATUS_CLOSED.equals(unmatchedPreviousIssue.getStatus())) { + relocateManualIssue(unmatched, unmatchedIssue, sourceHashHolder); + } + updateUnmatchedIssue(unmatched, false /* manual issues can be kept open */); + mergeTo.add(unmatched); + } + } + + private static void addUnmatchedFromReport(Iterable<TrackedIssue> rawIssues, Collection<TrackedIssue> trackedIssues, Date analysisDate) { + for (TrackedIssue rawIssue : rawIssues) { + rawIssue.setCreationDate(analysisDate); + trackedIssues.add(rawIssue); + } + } + + private void addIssuesOnDeletedComponents(Collection<TrackedIssue> issues) { + for (org.sonar.scanner.protocol.input.ScannerInput.ServerIssue previous : serverIssueRepository.issuesOnMissingComponents()) { + TrackedIssue dead = IssueTransformer.toTrackedIssue(previous); + updateUnmatchedIssue(dead, true); + issues.add(dead); + } + } + + private void updateUnmatchedIssue(TrackedIssue issue, boolean forceEndOfLife) { + ActiveRule activeRule = activeRules.find(issue.getRuleKey()); + issue.setNew(false); + + boolean manualIssue = issue.getRuleKey().isManual(); + boolean isRemovedRule = activeRule == null; + + if (isRemovedRule) { + IssueTransformer.resolveRemove(issue); + } else if (forceEndOfLife || !manualIssue) { + IssueTransformer.close(issue); + } + } + + private static void relocateManualIssue(TrackedIssue newIssue, ServerIssueFromWs oldIssue, SourceHashHolder sourceHashHolder) { + Integer previousLine = oldIssue.getLine(); + if (previousLine == null) { + return; + } + + Collection<Integer> newLinesWithSameHash = sourceHashHolder.getNewLinesMatching(previousLine); + if (newLinesWithSameHash.isEmpty()) { + if (previousLine > sourceHashHolder.getHashedSource().length()) { + IssueTransformer.resolveRemove(newIssue); + } + } else if (newLinesWithSameHash.size() == 1) { + Integer newLine = newLinesWithSameHash.iterator().next(); + newIssue.setStartLine(newLine); + newIssue.setEndLine(newLine); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/RollingFileHashes.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/RollingFileHashes.java new file mode 100644 index 00000000000..aaf2ba7bdf5 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/RollingFileHashes.java @@ -0,0 +1,89 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.tracking; + +/** + * Compute hashes of block around each line + */ +public class RollingFileHashes { + + final int[] rollingHashes; + + private RollingFileHashes(int[] hashes) { + this.rollingHashes = hashes; + } + + public static RollingFileHashes create(FileHashes hashes, int halfBlockSize) { + int size = hashes.length(); + int[] rollingHashes = new int[size]; + + RollingHashCalculator hashCalulator = new RollingHashCalculator(halfBlockSize * 2 + 1); + for (int i = 1; i <= Math.min(size, halfBlockSize + 1); i++) { + hashCalulator.add(hashes.getHash(i).hashCode()); + } + for (int i = 1; i <= size; i++) { + rollingHashes[i - 1] = hashCalulator.getHash(); + if (i - halfBlockSize > 0) { + hashCalulator.remove(hashes.getHash(i - halfBlockSize).hashCode()); + } + if (i + 1 + halfBlockSize <= size) { + hashCalulator.add(hashes.getHash(i + 1 + halfBlockSize).hashCode()); + } else { + hashCalulator.add(0); + } + } + + return new RollingFileHashes(rollingHashes); + } + + public int getHash(int line) { + return rollingHashes[line - 1]; + } + + private static class RollingHashCalculator { + + private static final int PRIME_BASE = 31; + + private final int power; + private int hash; + + public RollingHashCalculator(int size) { + int pow = 1; + for (int i = 0; i < size - 1; i++) { + pow = pow * PRIME_BASE; + } + this.power = pow; + } + + public void add(int value) { + hash = hash * PRIME_BASE + value; + } + + public void remove(int value) { + hash = hash - power * value; + } + + public int getHash() { + return hash; + } + + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/ServerIssueFromWs.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/ServerIssueFromWs.java new file mode 100644 index 00000000000..7898d5c88eb --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/ServerIssueFromWs.java @@ -0,0 +1,65 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.tracking; + +import javax.annotation.CheckForNull; + +import org.sonar.core.issue.tracking.Trackable; +import org.sonar.api.rule.RuleKey; + +public class ServerIssueFromWs implements Trackable { + + private org.sonar.scanner.protocol.input.ScannerInput.ServerIssue dto; + + public ServerIssueFromWs(org.sonar.scanner.protocol.input.ScannerInput.ServerIssue dto) { + this.dto = dto; + } + + public org.sonar.scanner.protocol.input.ScannerInput.ServerIssue getDto() { + return dto; + } + + public String key() { + return dto.getKey(); + } + + @Override + public RuleKey getRuleKey() { + return RuleKey.of(dto.getRuleRepository(), dto.getRuleKey()); + } + + @Override + @CheckForNull + public String getLineHash() { + return dto.hasChecksum() ? dto.getChecksum() : null; + } + + @Override + @CheckForNull + public Integer getLine() { + return dto.hasLine() ? dto.getLine() : null; + } + + @Override + public String getMessage() { + return dto.hasMsg() ? dto.getMsg() : ""; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/ServerIssueRepository.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/ServerIssueRepository.java new file mode 100644 index 00000000000..16f104583fd --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/ServerIssueRepository.java @@ -0,0 +1,92 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.tracking; + +import com.google.common.base.Function; +import javax.annotation.Nullable; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.InstantiationStrategy; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.api.utils.log.Profiler; +import org.sonar.batch.index.BatchComponent; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.index.Cache; +import org.sonar.batch.index.Caches; +import org.sonar.batch.repository.ServerIssuesLoader; +import org.sonar.batch.scan.ImmutableProjectReactor; +import org.sonar.core.component.ComponentKeys; +import org.sonar.scanner.protocol.input.ScannerInput.ServerIssue; + +@InstantiationStrategy(InstantiationStrategy.PER_BATCH) +@BatchSide +public class ServerIssueRepository { + + private static final Logger LOG = Loggers.get(ServerIssueRepository.class); + private static final String LOG_MSG = "Load server issues"; + + private final Caches caches; + private Cache<ServerIssue> issuesCache; + private final ServerIssuesLoader previousIssuesLoader; + private final ImmutableProjectReactor reactor; + private final BatchComponentCache resourceCache; + + public ServerIssueRepository(Caches caches, ServerIssuesLoader previousIssuesLoader, ImmutableProjectReactor reactor, BatchComponentCache resourceCache) { + this.caches = caches; + this.previousIssuesLoader = previousIssuesLoader; + this.reactor = reactor; + this.resourceCache = resourceCache; + } + + public void load() { + Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG); + this.issuesCache = caches.createCache("previousIssues"); + caches.registerValueCoder(ServerIssue.class, new ServerIssueValueCoder()); + boolean fromCache = previousIssuesLoader.load(reactor.getRoot().getKeyWithBranch(), new SaveIssueConsumer()); + profiler.stopInfo(fromCache); + } + + public Iterable<ServerIssue> byComponent(BatchComponent component) { + return issuesCache.values(component.batchId()); + } + + private class SaveIssueConsumer implements Function<ServerIssue, Void> { + + @Override + public Void apply(@Nullable ServerIssue issue) { + if (issue == null) { + return null; + } + String componentKey = ComponentKeys.createEffectiveKey(issue.getModuleKey(), issue.hasPath() ? issue.getPath() : null); + BatchComponent r = resourceCache.get(componentKey); + if (r == null) { + // Deleted resource + issuesCache.put(0, issue.getKey(), issue); + } else { + issuesCache.put(r.batchId(), issue.getKey(), issue); + } + return null; + } + } + + public Iterable<ServerIssue> issuesOnMissingComponents() { + return issuesCache.values(0); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/ServerIssueValueCoder.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/ServerIssueValueCoder.java new file mode 100644 index 00000000000..44876e4761c --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/ServerIssueValueCoder.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.tracking; + +import com.persistit.Value; +import com.persistit.encoding.CoderContext; +import com.persistit.encoding.ValueCoder; +import java.io.IOException; +import org.sonar.scanner.protocol.input.ScannerInput.ServerIssue; + +public class ServerIssueValueCoder implements ValueCoder { + + @Override + public void put(Value value, Object object, CoderContext context) { + ServerIssue issue = (ServerIssue) object; + value.putByteArray(issue.toByteArray()); + } + + @Override + public Object get(Value value, Class<?> clazz, CoderContext context) { + try { + return ServerIssue.parseFrom(value.getByteArray()); + } catch (IOException e) { + throw new IllegalStateException("Unable to read issue from cache", e); + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/ServerLineHashesLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/ServerLineHashesLoader.java new file mode 100644 index 00000000000..fb0b32d0d2b --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/ServerLineHashesLoader.java @@ -0,0 +1,32 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.tracking; + +import org.apache.commons.lang.mutable.MutableBoolean; + +import javax.annotation.Nullable; + +import org.sonar.api.batch.BatchSide; + +@BatchSide +public interface ServerLineHashesLoader { + + String[] getLineHashes(String fileKey, @Nullable MutableBoolean fromCache); +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/SourceHashHolder.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/SourceHashHolder.java new file mode 100644 index 00000000000..583a92f0b9d --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/SourceHashHolder.java @@ -0,0 +1,77 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.tracking; + +import com.google.common.collect.ImmutableSet; +import org.sonar.api.batch.fs.InputFile.Status; +import org.sonar.api.batch.fs.internal.DefaultInputFile; + +import javax.annotation.CheckForNull; + +import java.util.Collection; + +public class SourceHashHolder { + + private final ServerLineHashesLoader lastSnapshots; + + private FileHashes hashedReference; + private FileHashes hashedSource; + private DefaultInputFile inputFile; + + public SourceHashHolder(DefaultInputFile inputFile, ServerLineHashesLoader lastSnapshots) { + this.inputFile = inputFile; + this.lastSnapshots = lastSnapshots; + } + + private void initHashes() { + if (hashedSource == null) { + hashedSource = FileHashes.create(inputFile); + Status status = inputFile.status(); + if (status == Status.ADDED) { + hashedReference = null; + } else if (status == Status.SAME) { + hashedReference = hashedSource; + } else { + String[] lineHashes = lastSnapshots.getLineHashes(inputFile.key(), null); + hashedReference = lineHashes != null ? FileHashes.create(lineHashes) : null; + } + } + } + + @CheckForNull + public FileHashes getHashedReference() { + initHashes(); + return hashedReference; + } + + public FileHashes getHashedSource() { + initHashes(); + return hashedSource; + } + + public Collection<Integer> getNewLinesMatching(Integer originLine) { + FileHashes reference = getHashedReference(); + if (reference == null) { + return ImmutableSet.of(); + } else { + return getHashedSource().getLinesForHash(reference.getHash(originLine)); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/TrackedIssue.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/TrackedIssue.java new file mode 100644 index 00000000000..ea303bf6a4f --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/TrackedIssue.java @@ -0,0 +1,262 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.tracking; + +import com.google.common.base.Preconditions; +import java.io.Serializable; +import java.util.Date; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; +import org.sonar.api.rule.RuleKey; +import org.sonar.core.issue.tracking.Trackable; + +public class TrackedIssue implements Trackable, Serializable { + private static final long serialVersionUID = -1755017079070964287L; + + private RuleKey ruleKey; + private String key; + private String severity; + private Integer startLine; + private Integer startLineOffset; + private Integer endLine; + private Integer endLineOffset; + private Double gap; + private boolean isNew; + private Date creationDate; + private String resolution; + private String status; + private String assignee; + private String reporter; + private String componentKey; + private String message; + + private transient FileHashes hashes; + + public TrackedIssue() { + hashes = null; + } + + public TrackedIssue(@Nullable FileHashes hashes) { + this.hashes = hashes; + } + + @Override + @CheckForNull + public String getLineHash() { + if (getLine() == null || hashes == null) { + return null; + } + + int line = getLine(); + Preconditions.checkState(line <= hashes.length(), "Invalid line number for issue %s. File has only %s line(s)", this, hashes.length()); + + return hashes.getHash(line); + } + + @Override + public String getMessage() { + return message; + } + + public TrackedIssue setMessage(String message) { + this.message = message; + return this; + } + + public String componentKey() { + return componentKey; + } + + public TrackedIssue setComponentKey(String componentKey) { + this.componentKey = componentKey; + return this; + } + + public String key() { + return key; + } + + public Integer startLine() { + return startLine; + } + + @Override + public Integer getLine() { + return startLine; + } + + public TrackedIssue setStartLine(Integer startLine) { + this.startLine = startLine; + return this; + } + + public Integer startLineOffset() { + return startLineOffset; + } + + public TrackedIssue setStartLineOffset(Integer startLineOffset) { + this.startLineOffset = startLineOffset; + return this; + } + + public Integer endLine() { + return endLine; + } + + public TrackedIssue setEndLine(Integer endLine) { + this.endLine = endLine; + return this; + } + + public Integer endLineOffset() { + return endLineOffset; + } + + public TrackedIssue setEndLineOffset(Integer endLineOffset) { + this.endLineOffset = endLineOffset; + return this; + } + + public TrackedIssue setKey(String key) { + this.key = key; + return this; + } + + public String assignee() { + return assignee; + } + + public TrackedIssue setAssignee(String assignee) { + this.assignee = assignee; + return this; + } + + public String reporter() { + return reporter; + } + + public TrackedIssue setReporter(String reporter) { + this.reporter = reporter; + return this; + } + + public String resolution() { + return resolution; + } + + public TrackedIssue setResolution(String resolution) { + this.resolution = resolution; + return this; + } + + public String status() { + return status; + } + + public TrackedIssue setStatus(String status) { + this.status = status; + return this; + } + + @Override + public RuleKey getRuleKey() { + return ruleKey; + } + + public String severity() { + return severity; + } + + public Double gap() { + return gap; + } + + public Date getCreationDate() { + return creationDate; + } + + public boolean isNew() { + return isNew; + } + + public TrackedIssue setNew(boolean isNew) { + this.isNew = isNew; + return this; + } + + public Date creationDate() { + return creationDate; + } + + public TrackedIssue setCreationDate(Date creationDate) { + this.creationDate = creationDate; + return this; + } + + public TrackedIssue setRuleKey(RuleKey ruleKey) { + this.ruleKey = ruleKey; + return this; + } + + public TrackedIssue setSeverity(String severity) { + this.severity = severity; + return this; + } + + public TrackedIssue setGap(Double gap) { + this.gap = gap; + return this; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((key == null) ? 0 : key.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + TrackedIssue other = (TrackedIssue) obj; + if (key == null) { + if (other.key != null) { + return false; + } + } else if (!key.equals(other.key)) { + return false; + } + return true; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/package-info.java new file mode 100644 index 00000000000..148dc77f0f4 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/issue/tracking/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.issue.tracking; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/mediumtest/FakePluginInstaller.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/mediumtest/FakePluginInstaller.java new file mode 100644 index 00000000000..71205c7b08d --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/mediumtest/FakePluginInstaller.java @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import org.sonar.api.SonarPlugin; +import org.sonar.batch.bootstrap.PluginInstaller; +import org.sonar.core.platform.PluginInfo; + +public class FakePluginInstaller implements PluginInstaller { + public static final String MEDIUM_TEST_ENABLED = "sonar.mediumTest.enabled"; + + private final Map<String, PluginInfo> infosByKeys = new HashMap<>(); + private final Map<String, SonarPlugin> instancesByKeys = new HashMap<>(); + + public FakePluginInstaller add(String pluginKey, File jarFile) { + infosByKeys.put(pluginKey, PluginInfo.create(jarFile)); + return this; + } + + public FakePluginInstaller add(String pluginKey, SonarPlugin instance) { + instancesByKeys.put(pluginKey, instance); + return this; + } + + @Override + public Map<String, PluginInfo> installRemotes() { + return infosByKeys; + } + + @Override + public Map<String, SonarPlugin> installLocals() { + return instancesByKeys; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/mediumtest/ScanTaskObserver.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/mediumtest/ScanTaskObserver.java new file mode 100644 index 00000000000..05b9f338eef --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/mediumtest/ScanTaskObserver.java @@ -0,0 +1,32 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest; + +import org.sonar.api.batch.BatchSide; +import org.sonar.api.ExtensionPoint; +import org.sonar.batch.scan.ProjectScanContainer; + +@BatchSide +@ExtensionPoint +public interface ScanTaskObserver { + + void scanTaskCompleted(ProjectScanContainer container); + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/mediumtest/ScanTaskObservers.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/mediumtest/ScanTaskObservers.java new file mode 100644 index 00000000000..90e092cad77 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/mediumtest/ScanTaskObservers.java @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest; + +import org.sonar.batch.scan.ProjectScanContainer; + +public class ScanTaskObservers { + + private ScanTaskObserver[] observers; + private ProjectScanContainer projectScanContainer; + + public ScanTaskObservers(ProjectScanContainer projectScanContainer, ScanTaskObserver... observers) { + this.projectScanContainer = projectScanContainer; + this.observers = observers; + } + + public ScanTaskObservers(ProjectScanContainer projectScanContainer) { + this(projectScanContainer, new ScanTaskObserver[0]); + } + + public void notifyEndOfScanTask() { + for (ScanTaskObserver scanTaskObserver : observers) { + scanTaskObserver.scanTaskCompleted(projectScanContainer); + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/mediumtest/TaskResult.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/mediumtest/TaskResult.java new file mode 100644 index 00000000000..5d3d87b540d --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/mediumtest/TaskResult.java @@ -0,0 +1,292 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest; + +import com.google.common.collect.Iterators; +import com.google.common.collect.Lists; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.api.batch.fs.InputComponent; +import org.sonar.api.batch.fs.InputDir; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.TextPointer; +import org.sonar.api.batch.fs.TextRange; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.sensor.highlighting.TypeOfText; +import org.sonar.batch.issue.IssueCache; +import org.sonar.batch.issue.tracking.TrackedIssue; +import org.sonar.batch.report.ScannerReportUtils; +import org.sonar.batch.report.ReportPublisher; +import org.sonar.batch.scan.ProjectScanContainer; +import org.sonar.batch.scan.filesystem.InputPathCache; +import org.sonar.core.util.CloseableIterator; +import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.scanner.protocol.output.ScannerReportReader; +import org.sonar.scanner.protocol.output.ScannerReport.Component; +import org.sonar.scanner.protocol.output.ScannerReport.Metadata; +import org.sonar.scanner.protocol.output.ScannerReport.Symbol; + +public class TaskResult implements org.sonar.batch.mediumtest.ScanTaskObserver { + + private static final Logger LOG = LoggerFactory.getLogger(TaskResult.class); + + private List<TrackedIssue> issues = new ArrayList<>(); + private Map<String, InputFile> inputFiles = new HashMap<>(); + private Map<String, Component> reportComponents = new HashMap<>(); + private Map<String, InputDir> inputDirs = new HashMap<>(); + private ScannerReportReader reader; + + @Override + public void scanTaskCompleted(ProjectScanContainer container) { + LOG.info("Store analysis results in memory for later assertions in medium test"); + for (TrackedIssue issue : container.getComponentByType(IssueCache.class).all()) { + issues.add(issue); + } + + ReportPublisher reportPublisher = container.getComponentByType(ReportPublisher.class); + reader = new ScannerReportReader(reportPublisher.getReportDir()); + if (!container.getComponentByType(AnalysisMode.class).isIssues()) { + Metadata readMetadata = getReportReader().readMetadata(); + int rootComponentRef = readMetadata.getRootComponentRef(); + storeReportComponents(rootComponentRef, null, readMetadata.hasBranch() ? readMetadata.getBranch() : null); + } + + storeFs(container); + + } + + private void storeReportComponents(int componentRef, String parentModuleKey, @Nullable String branch) { + Component component = getReportReader().readComponent(componentRef); + if (component.hasKey()) { + reportComponents.put(component.getKey() + (branch != null ? ":" + branch : ""), component); + } else { + reportComponents.put(parentModuleKey + (branch != null ? ":" + branch : "") + ":" + component.getPath(), component); + } + for (int childId : component.getChildRefList()) { + storeReportComponents(childId, component.hasKey() ? component.getKey() : parentModuleKey, branch); + } + + } + + public ScannerReportReader getReportReader() { + return reader; + } + + private void storeFs(ProjectScanContainer container) { + InputPathCache inputFileCache = container.getComponentByType(InputPathCache.class); + for (InputFile inputPath : inputFileCache.allFiles()) { + inputFiles.put(inputPath.relativePath(), inputPath); + } + for (InputDir inputPath : inputFileCache.allDirs()) { + inputDirs.put(inputPath.relativePath(), inputPath); + } + } + + public List<TrackedIssue> trackedIssues() { + return issues; + } + + public Component getReportComponent(String key) { + return reportComponents.get(key); + } + + public List<ScannerReport.Issue> issuesFor(InputComponent inputComponent) { + int ref = reportComponents.get(inputComponent.key()).getRef(); + return issuesFor(ref); + } + + public List<ScannerReport.Issue> issuesFor(Component reportComponent) { + int ref = reportComponent.getRef(); + return issuesFor(ref); + } + + private List<ScannerReport.Issue> issuesFor(int ref) { + List<ScannerReport.Issue> result = Lists.newArrayList(); + try (CloseableIterator<ScannerReport.Issue> it = reader.readComponentIssues(ref)) { + while (it.hasNext()) { + result.add(it.next()); + } + } + return result; + } + + public Collection<InputFile> inputFiles() { + return inputFiles.values(); + } + + @CheckForNull + public InputFile inputFile(String relativePath) { + return inputFiles.get(relativePath); + } + + public Collection<InputDir> inputDirs() { + return inputDirs.values(); + } + + @CheckForNull + public InputDir inputDir(String relativePath) { + return inputDirs.get(relativePath); + } + + public Map<String, List<ScannerReport.Measure>> allMeasures() { + Map<String, List<ScannerReport.Measure>> result = new HashMap<>(); + for (Map.Entry<String, Component> component : reportComponents.entrySet()) { + List<ScannerReport.Measure> measures = new ArrayList<>(); + try (CloseableIterator<ScannerReport.Measure> it = reader.readComponentMeasures(component.getValue().getRef())) { + Iterators.addAll(measures, it); + } + result.put(component.getKey(), measures); + } + return result; + } + + /** + * Get highlighting types at a given position in an inputfile + * @param lineOffset 0-based offset in file + */ + public List<TypeOfText> highlightingTypeFor(InputFile file, int line, int lineOffset) { + int ref = reportComponents.get(((DefaultInputFile) file).key()).getRef(); + if (!reader.hasSyntaxHighlighting(ref)) { + return Collections.emptyList(); + } + TextPointer pointer = file.newPointer(line, lineOffset); + List<TypeOfText> result = new ArrayList<>(); + try (CloseableIterator<ScannerReport.SyntaxHighlighting> it = reader.readComponentSyntaxHighlighting(ref)) { + while (it.hasNext()) { + ScannerReport.SyntaxHighlighting rule = it.next(); + TextRange ruleRange = toRange(file, rule.getRange()); + if (ruleRange.start().compareTo(pointer) <= 0 && ruleRange.end().compareTo(pointer) > 0) { + result.add(ScannerReportUtils.toBatchType(rule.getType())); + } + } + } catch (Exception e) { + throw new IllegalStateException("Can't read syntax highlighting for " + file.absolutePath(), e); + } + return result; + } + + private static TextRange toRange(InputFile file, ScannerReport.TextRange reportRange) { + return file.newRange(file.newPointer(reportRange.getStartLine(), reportRange.getStartOffset()), file.newPointer(reportRange.getEndLine(), reportRange.getEndOffset())); + } + + /** + * Get list of all start positions of a symbol in an inputfile + * @param symbolStartLine 0-based start offset for the symbol in file + * @param symbolStartLineOffset 0-based end offset for the symbol in file + */ + @CheckForNull + public List<ScannerReport.TextRange> symbolReferencesFor(InputFile file, int symbolStartLine, int symbolStartLineOffset) { + int ref = reportComponents.get(((DefaultInputFile) file).key()).getRef(); + try (CloseableIterator<Symbol> symbols = getReportReader().readComponentSymbols(ref)) { + while (symbols.hasNext()) { + Symbol symbol = symbols.next(); + if (symbol.getDeclaration().getStartLine() == symbolStartLine && symbol.getDeclaration().getStartOffset() == symbolStartLineOffset) { + return symbol.getReferenceList(); + } + } + } + return Collections.emptyList(); + } + + public List<ScannerReport.Duplication> duplicationsFor(InputFile file) { + List<ScannerReport.Duplication> result = new ArrayList<>(); + int ref = reportComponents.get(((DefaultInputFile) file).key()).getRef(); + try (CloseableIterator<ScannerReport.Duplication> it = getReportReader().readComponentDuplications(ref)) { + while (it.hasNext()) { + result.add(it.next()); + } + } catch (Exception e) { + throw new IllegalStateException(e); + } + return result; + } + + public List<ScannerReport.CpdTextBlock> duplicationBlocksFor(InputFile file) { + List<ScannerReport.CpdTextBlock> result = new ArrayList<>(); + int ref = reportComponents.get(((DefaultInputFile) file).key()).getRef(); + try (CloseableIterator<ScannerReport.CpdTextBlock> it = getReportReader().readCpdTextBlocks(ref)) { + while (it.hasNext()) { + result.add(it.next()); + } + } catch (Exception e) { + throw new IllegalStateException(e); + } + return result; + } + + @CheckForNull + public ScannerReport.Coverage coverageFor(InputFile file, int line) { + int ref = reportComponents.get(((DefaultInputFile) file).key()).getRef(); + try (CloseableIterator<ScannerReport.Coverage> it = getReportReader().readComponentCoverage(ref)) { + while (it.hasNext()) { + ScannerReport.Coverage coverage = it.next(); + if (coverage.getLine() == line) { + return coverage; + } + } + } catch (Exception e) { + throw new IllegalStateException(e); + } + return null; + } + + public ScannerReport.Test testExecutionFor(InputFile testFile, String testName) { + int ref = reportComponents.get(((DefaultInputFile) testFile).key()).getRef(); + try (InputStream inputStream = FileUtils.openInputStream(getReportReader().readTests(ref))) { + ScannerReport.Test test = ScannerReport.Test.PARSER.parseDelimitedFrom(inputStream); + while (test != null) { + if (test.getName().equals(testName)) { + return test; + } + test = ScannerReport.Test.PARSER.parseDelimitedFrom(inputStream); + } + } catch (Exception e) { + throw new IllegalStateException(e); + } + return null; + } + + public ScannerReport.CoverageDetail coveragePerTestFor(InputFile testFile, String testName) { + int ref = reportComponents.get(((DefaultInputFile) testFile).key()).getRef(); + try (InputStream inputStream = FileUtils.openInputStream(getReportReader().readCoverageDetails(ref))) { + ScannerReport.CoverageDetail details = ScannerReport.CoverageDetail.PARSER.parseDelimitedFrom(inputStream); + while (details != null) { + if (details.getTestName().equals(testName)) { + return details; + } + details = ScannerReport.CoverageDetail.PARSER.parseDelimitedFrom(inputStream); + } + } catch (Exception e) { + throw new IllegalStateException(e); + } + return null; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/mediumtest/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/mediumtest/package-info.java new file mode 100644 index 00000000000..2cc10ab66ff --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/mediumtest/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.mediumtest; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/package-info.java new file mode 100644 index 00000000000..408dd9f93a6 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/AbstractPhaseEvent.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/AbstractPhaseEvent.java new file mode 100644 index 00000000000..df8bc690534 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/AbstractPhaseEvent.java @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.phases; + +import org.sonar.api.batch.events.EventHandler; +import org.sonar.batch.events.BatchEvent; + +public abstract class AbstractPhaseEvent<H extends EventHandler> extends BatchEvent<H> { + + private final boolean start; + + public AbstractPhaseEvent(boolean start) { + this.start = start; + } + + public final boolean isStart() { + return start; + } + + public final boolean isEnd() { + return !start; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/AbstractPhaseExecutor.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/AbstractPhaseExecutor.java new file mode 100644 index 00000000000..c388b75f1f3 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/AbstractPhaseExecutor.java @@ -0,0 +1,120 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.phases; + +import org.sonar.api.batch.SensorContext; +import org.sonar.api.resources.Project; +import org.sonar.batch.events.BatchStepEvent; +import org.sonar.batch.events.EventBus; +import org.sonar.batch.index.DefaultIndex; +import org.sonar.batch.issue.ignore.scanner.IssueExclusionsLoader; +import org.sonar.batch.rule.QProfileVerifier; +import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem; +import org.sonar.batch.scan.filesystem.FileSystemLogger; + +public abstract class AbstractPhaseExecutor { + + private final EventBus eventBus; + private final PostJobsExecutor postJobsExecutor; + private final InitializersExecutor initializersExecutor; + private final SensorsExecutor sensorsExecutor; + private final SensorContext sensorContext; + private final DefaultIndex index; + private final ProjectInitializer pi; + private final FileSystemLogger fsLogger; + private final DefaultModuleFileSystem fs; + private final QProfileVerifier profileVerifier; + private final IssueExclusionsLoader issueExclusionsLoader; + + public AbstractPhaseExecutor(InitializersExecutor initializersExecutor, PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor, + SensorContext sensorContext, DefaultIndex index, + EventBus eventBus, ProjectInitializer pi, + FileSystemLogger fsLogger, DefaultModuleFileSystem fs, QProfileVerifier profileVerifier, + IssueExclusionsLoader issueExclusionsLoader) { + this.postJobsExecutor = postJobsExecutor; + this.initializersExecutor = initializersExecutor; + this.sensorsExecutor = sensorsExecutor; + this.sensorContext = sensorContext; + this.index = index; + this.eventBus = eventBus; + this.pi = pi; + this.fsLogger = fsLogger; + this.fs = fs; + this.profileVerifier = profileVerifier; + this.issueExclusionsLoader = issueExclusionsLoader; + } + + /** + * Executed on each module + */ + public final void execute(Project module) { + pi.execute(module); + + eventBus.fireEvent(new ProjectAnalysisEvent(module, true)); + + executeInitializersPhase(); + + // Index and lock the filesystem + indexFs(); + + // Log detected languages and their profiles after FS is indexed and languages detected + profileVerifier.execute(); + + // Initialize issue exclusions + initIssueExclusions(); + + sensorsExecutor.execute(sensorContext); + + if (module.isRoot()) { + executeOnRoot(); + postJobsExecutor.execute(sensorContext); + } + cleanMemory(); + eventBus.fireEvent(new ProjectAnalysisEvent(module, false)); + } + + protected abstract void executeOnRoot(); + + private void initIssueExclusions() { + String stepName = "Init issue exclusions"; + eventBus.fireEvent(new BatchStepEvent(stepName, true)); + issueExclusionsLoader.execute(); + eventBus.fireEvent(new BatchStepEvent(stepName, false)); + } + + private void indexFs() { + String stepName = "Index filesystem"; + eventBus.fireEvent(new BatchStepEvent(stepName, true)); + fs.index(); + eventBus.fireEvent(new BatchStepEvent(stepName, false)); + } + + private void executeInitializersPhase() { + initializersExecutor.execute(); + fsLogger.log(); + } + + private void cleanMemory() { + String cleanMemory = "Clean memory"; + eventBus.fireEvent(new BatchStepEvent(cleanMemory, true)); + index.clear(); + eventBus.fireEvent(new BatchStepEvent(cleanMemory, false)); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/InitializerExecutionEvent.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/InitializerExecutionEvent.java new file mode 100644 index 00000000000..9a8bdc92706 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/InitializerExecutionEvent.java @@ -0,0 +1,50 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.phases; + +import org.sonar.api.batch.Initializer; +import org.sonar.api.batch.events.InitializerExecutionHandler; + +class InitializerExecutionEvent extends AbstractPhaseEvent<InitializerExecutionHandler> + implements org.sonar.api.batch.events.InitializerExecutionHandler.InitializerExecutionEvent { + + private final Initializer initializer; + + InitializerExecutionEvent(Initializer initializer, boolean start) { + super(start); + this.initializer = initializer; + } + + @Override + public Initializer getInitializer() { + return initializer; + } + + @Override + public void dispatch(InitializerExecutionHandler handler) { + handler.onInitializerExecution(this); + } + + @Override + public Class getType() { + return InitializerExecutionHandler.class; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/InitializersExecutor.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/InitializersExecutor.java new file mode 100644 index 00000000000..591969ea1b0 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/InitializersExecutor.java @@ -0,0 +1,67 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.phases; + +import com.google.common.collect.Lists; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.batch.Initializer; +import org.sonar.api.resources.Project; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.api.utils.log.Profiler; +import org.sonar.batch.bootstrap.BatchExtensionDictionnary; +import org.sonar.batch.events.EventBus; + +import java.util.Collection; + +public class InitializersExecutor { + + private static final Logger LOG = Loggers.get(SensorsExecutor.class); + + private Project project; + private BatchExtensionDictionnary selector; + private EventBus eventBus; + + public InitializersExecutor(BatchExtensionDictionnary selector, Project project, EventBus eventBus) { + this.selector = selector; + this.project = project; + this.eventBus = eventBus; + } + + public void execute() { + Collection<Initializer> initializers = selector.select(Initializer.class, project, true, null); + eventBus.fireEvent(new InitializersPhaseEvent(Lists.newArrayList(initializers), true)); + if (LOG.isDebugEnabled()) { + LOG.debug("Initializers : {}", StringUtils.join(initializers, " -> ")); + } + + for (Initializer initializer : initializers) { + eventBus.fireEvent(new InitializerExecutionEvent(initializer, true)); + + Profiler profiler = Profiler.create(LOG).startInfo("Initializer " + initializer); + initializer.execute(project); + profiler.stopInfo(); + eventBus.fireEvent(new InitializerExecutionEvent(initializer, false)); + } + + eventBus.fireEvent(new InitializersPhaseEvent(Lists.newArrayList(initializers), false)); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/InitializersPhaseEvent.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/InitializersPhaseEvent.java new file mode 100644 index 00000000000..5e109625da6 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/InitializersPhaseEvent.java @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.phases; + +import org.sonar.api.batch.Initializer; +import org.sonar.api.batch.events.InitializersPhaseHandler; + +import java.util.List; + +class InitializersPhaseEvent extends AbstractPhaseEvent<InitializersPhaseHandler> + implements org.sonar.api.batch.events.InitializersPhaseHandler.InitializersPhaseEvent { + + private final List<Initializer> initializers; + + InitializersPhaseEvent(List<Initializer> initializers, boolean start) { + super(start); + this.initializers = initializers; + } + + @Override + public List<Initializer> getInitializers() { + return initializers; + } + + @Override + protected void dispatch(InitializersPhaseHandler handler) { + handler.onInitializersPhase(this); + } + + @Override + protected Class getType() { + return InitializersPhaseHandler.class; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/IssuesPhaseExecutor.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/IssuesPhaseExecutor.java new file mode 100644 index 00000000000..ba9a155962e --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/IssuesPhaseExecutor.java @@ -0,0 +1,84 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.phases; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.SensorContext; +import org.sonar.batch.events.BatchStepEvent; +import org.sonar.batch.events.EventBus; +import org.sonar.batch.index.DefaultIndex; +import org.sonar.batch.issue.IssueCallback; +import org.sonar.batch.issue.ignore.scanner.IssueExclusionsLoader; +import org.sonar.batch.issue.tracking.IssueTransition; +import org.sonar.batch.rule.QProfileVerifier; +import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem; +import org.sonar.batch.scan.filesystem.FileSystemLogger; +import org.sonar.batch.scan.report.IssuesReports; + +public final class IssuesPhaseExecutor extends AbstractPhaseExecutor { + + private static final Logger LOG = LoggerFactory.getLogger(IssuesPhaseExecutor.class); + + private final EventBus eventBus; + private final IssuesReports issuesReport; + private final IssueTransition localIssueTracking; + private final IssueCallback issueCallback; + + public IssuesPhaseExecutor(InitializersExecutor initializersExecutor, PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor, SensorContext sensorContext, + DefaultIndex index, EventBus eventBus, ProjectInitializer pi, FileSystemLogger fsLogger, IssuesReports jsonReport, DefaultModuleFileSystem fs, QProfileVerifier profileVerifier, + IssueExclusionsLoader issueExclusionsLoader, IssueTransition localIssueTracking, IssueCallback issueCallback) { + super(initializersExecutor, postJobsExecutor, sensorsExecutor, sensorContext, index, eventBus, pi, fsLogger, fs, profileVerifier, issueExclusionsLoader); + this.eventBus = eventBus; + this.issuesReport = jsonReport; + this.localIssueTracking = localIssueTracking; + this.issueCallback = issueCallback; + } + + @Override + protected void executeOnRoot() { + localIssueTracking(); + issuesCallback(); + issuesReport(); + LOG.info("ANALYSIS SUCCESSFUL"); + } + + private void localIssueTracking() { + String stepName = "Local Issue Tracking"; + eventBus.fireEvent(new BatchStepEvent(stepName, true)); + localIssueTracking.execute(); + eventBus.fireEvent(new BatchStepEvent(stepName, false)); + } + + private void issuesCallback() { + String stepName = "Issues Callback"; + eventBus.fireEvent(new BatchStepEvent(stepName, true)); + issueCallback.execute(); + eventBus.fireEvent(new BatchStepEvent(stepName, false)); + } + + private void issuesReport() { + String stepName = "Issues Reports"; + eventBus.fireEvent(new BatchStepEvent(stepName, true)); + issuesReport.execute(); + eventBus.fireEvent(new BatchStepEvent(stepName, false)); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/PhasesTimeProfiler.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/PhasesTimeProfiler.java new file mode 100644 index 00000000000..ba8c7c498d0 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/PhasesTimeProfiler.java @@ -0,0 +1,53 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.phases; + + +import org.sonar.batch.util.BatchUtils; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.batch.events.SensorExecutionHandler; +import org.sonar.api.batch.events.SensorsPhaseHandler; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.api.utils.log.Profiler; + +public class PhasesTimeProfiler implements SensorExecutionHandler, SensorsPhaseHandler { + + private static final Logger LOG = Loggers.get(PhasesTimeProfiler.class); + + private Profiler profiler = Profiler.create(LOG); + + @Override + public void onSensorsPhase(SensorsPhaseEvent event) { + if (event.isStart()) { + LOG.debug("Sensors : {}", StringUtils.join(event.getSensors(), " -> ")); + } + } + + @Override + public void onSensorExecution(SensorExecutionEvent event) { + if (event.isStart()) { + profiler.startInfo("Sensor " + BatchUtils.describe(event.getSensor())); + } else { + profiler.stopInfo(); + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/PostJobExecutionEvent.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/PostJobExecutionEvent.java new file mode 100644 index 00000000000..b7cd92a217b --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/PostJobExecutionEvent.java @@ -0,0 +1,50 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.phases; + +import org.sonar.api.batch.PostJob; +import org.sonar.api.batch.events.PostJobExecutionHandler; + +class PostJobExecutionEvent extends AbstractPhaseEvent<PostJobExecutionHandler> + implements org.sonar.api.batch.events.PostJobExecutionHandler.PostJobExecutionEvent { + + private final PostJob postJob; + + PostJobExecutionEvent(PostJob postJob, boolean start) { + super(start); + this.postJob = postJob; + } + + @Override + public PostJob getPostJob() { + return postJob; + } + + @Override + public void dispatch(PostJobExecutionHandler handler) { + handler.onPostJobExecution(this); + } + + @Override + public Class getType() { + return PostJobExecutionHandler.class; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/PostJobPhaseEvent.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/PostJobPhaseEvent.java new file mode 100644 index 00000000000..860d7cc938d --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/PostJobPhaseEvent.java @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.phases; + +import org.sonar.api.batch.PostJob; +import org.sonar.api.batch.events.PostJobsPhaseHandler; + +import java.util.List; + +class PostJobPhaseEvent extends AbstractPhaseEvent<PostJobsPhaseHandler> + implements org.sonar.api.batch.events.PostJobsPhaseHandler.PostJobsPhaseEvent { + + private final List<PostJob> postJobs; + + PostJobPhaseEvent(List<PostJob> postJobs, boolean start) { + super(start); + this.postJobs = postJobs; + } + + @Override + public List<PostJob> getPostJobs() { + return postJobs; + } + + @Override + protected void dispatch(PostJobsPhaseHandler handler) { + handler.onPostJobsPhase(this); + } + + @Override + protected Class getType() { + return PostJobsPhaseHandler.class; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/PostJobsExecutor.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/PostJobsExecutor.java new file mode 100644 index 00000000000..277b45b8d3c --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/PostJobsExecutor.java @@ -0,0 +1,75 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.phases; + +import org.sonar.batch.util.BatchUtils; + +import com.google.common.collect.Lists; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.PostJob; +import org.sonar.api.batch.SensorContext; +import org.sonar.api.resources.Project; +import org.sonar.batch.bootstrap.BatchExtensionDictionnary; +import org.sonar.batch.events.EventBus; + +import java.util.Collection; + +@BatchSide +public class PostJobsExecutor { + private static final Logger LOG = LoggerFactory.getLogger(PostJobsExecutor.class); + + private final BatchExtensionDictionnary selector; + private final Project project; + private final EventBus eventBus; + + public PostJobsExecutor(BatchExtensionDictionnary selector, Project project, EventBus eventBus) { + this.selector = selector; + this.project = project; + this.eventBus = eventBus; + } + + public void execute(SensorContext context) { + Collection<PostJob> postJobs = selector.select(PostJob.class, project, true, null); + + eventBus.fireEvent(new PostJobPhaseEvent(Lists.newArrayList(postJobs), true)); + execute(context, postJobs); + eventBus.fireEvent(new PostJobPhaseEvent(Lists.newArrayList(postJobs), false)); + } + + private void execute(SensorContext context, Collection<PostJob> postJobs) { + logPostJobs(postJobs); + + for (PostJob postJob : postJobs) { + LOG.info("Executing post-job {}", BatchUtils.describe(postJob)); + eventBus.fireEvent(new PostJobExecutionEvent(postJob, true)); + postJob.executeOn(project, context); + eventBus.fireEvent(new PostJobExecutionEvent(postJob, false)); + } + } + + private static void logPostJobs(Collection<PostJob> postJobs) { + if (LOG.isDebugEnabled()) { + LOG.debug("Post-jobs : {}", StringUtils.join(postJobs, " -> ")); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/ProjectAnalysisEvent.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/ProjectAnalysisEvent.java new file mode 100644 index 00000000000..428774acd82 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/ProjectAnalysisEvent.java @@ -0,0 +1,50 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.phases; + +import org.sonar.api.batch.events.ProjectAnalysisHandler; +import org.sonar.api.resources.Project; + +class ProjectAnalysisEvent extends AbstractPhaseEvent<ProjectAnalysisHandler> + implements org.sonar.api.batch.events.ProjectAnalysisHandler.ProjectAnalysisEvent { + + private final Project project; + + ProjectAnalysisEvent(Project project, boolean start) { + super(start); + this.project = project; + } + + @Override + public Project getProject() { + return project; + } + + @Override + protected void dispatch(ProjectAnalysisHandler handler) { + handler.onProjectAnalysis(this); + } + + @Override + protected Class getType() { + return ProjectAnalysisHandler.class; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/ProjectInitializer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/ProjectInitializer.java new file mode 100644 index 00000000000..8dac11520e5 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/ProjectInitializer.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.phases; + +import org.sonar.api.batch.BatchSide; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.CoreProperties; +import org.sonar.api.config.Settings; +import org.sonar.api.resources.Language; +import org.sonar.api.resources.Languages; +import org.sonar.api.resources.Project; +import org.sonar.api.utils.MessageException; + +/** + * Should be dropped when org.sonar.api.resources.Project is fully refactored. + */ +@BatchSide +public class ProjectInitializer { + + private Languages languages; + private Settings settings; + + public ProjectInitializer(Settings settings, Languages languages) { + this.settings = settings; + this.languages = languages; + } + + public void execute(Project project) { + if (project.getLanguage() == null) { + initDeprecatedLanguage(project); + } + } + + private void initDeprecatedLanguage(Project project) { + String languageKey = settings.getString(CoreProperties.PROJECT_LANGUAGE_PROPERTY); + if (StringUtils.isNotBlank(languageKey)) { + Language language = languages.get(languageKey); + if (language == null) { + throw MessageException.of("Language with key '" + languageKey + "' not found"); + } + project.setLanguage(language); + } else { + project.setLanguage(Project.NONE_LANGUAGE); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/PublishPhaseExecutor.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/PublishPhaseExecutor.java new file mode 100644 index 00000000000..33073a110b3 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/PublishPhaseExecutor.java @@ -0,0 +1,67 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.phases; + +import org.sonar.api.batch.SensorContext; +import org.sonar.batch.cpd.CpdExecutor; +import org.sonar.batch.events.BatchStepEvent; +import org.sonar.batch.events.EventBus; +import org.sonar.batch.index.DefaultIndex; +import org.sonar.batch.issue.ignore.scanner.IssueExclusionsLoader; +import org.sonar.batch.report.ReportPublisher; +import org.sonar.batch.rule.QProfileVerifier; +import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem; +import org.sonar.batch.scan.filesystem.FileSystemLogger; + +public final class PublishPhaseExecutor extends AbstractPhaseExecutor { + + private final EventBus eventBus; + private final ReportPublisher reportPublisher; + private final CpdExecutor cpdExecutor; + + public PublishPhaseExecutor(InitializersExecutor initializersExecutor, PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor, SensorContext sensorContext, + DefaultIndex index, EventBus eventBus, ReportPublisher reportPublisher, ProjectInitializer pi, FileSystemLogger fsLogger, DefaultModuleFileSystem fs, + QProfileVerifier profileVerifier, IssueExclusionsLoader issueExclusionsLoader, CpdExecutor cpdExecutor) { + super(initializersExecutor, postJobsExecutor, sensorsExecutor, sensorContext, index, eventBus, pi, fsLogger, fs, profileVerifier, issueExclusionsLoader); + this.eventBus = eventBus; + this.reportPublisher = reportPublisher; + this.cpdExecutor = cpdExecutor; + } + + @Override + protected void executeOnRoot() { + computeDuplications(); + publishReportJob(); + } + + private void computeDuplications() { + String stepName = "Computing duplications"; + eventBus.fireEvent(new BatchStepEvent(stepName, true)); + cpdExecutor.execute(); + eventBus.fireEvent(new BatchStepEvent(stepName, false)); + } + + private void publishReportJob() { + String stepName = "Publish report"; + eventBus.fireEvent(new BatchStepEvent(stepName, true)); + this.reportPublisher.execute(); + eventBus.fireEvent(new BatchStepEvent(stepName, false)); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/SensorExecutionEvent.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/SensorExecutionEvent.java new file mode 100644 index 00000000000..22cbd25aadb --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/SensorExecutionEvent.java @@ -0,0 +1,50 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.phases; + +import org.sonar.api.batch.Sensor; +import org.sonar.api.batch.events.SensorExecutionHandler; + +class SensorExecutionEvent extends AbstractPhaseEvent<SensorExecutionHandler> + implements org.sonar.api.batch.events.SensorExecutionHandler.SensorExecutionEvent { + + private final Sensor sensor; + + SensorExecutionEvent(Sensor sensor, boolean start) { + super(start); + this.sensor = sensor; + } + + @Override + public Sensor getSensor() { + return sensor; + } + + @Override + public void dispatch(SensorExecutionHandler handler) { + handler.onSensorExecution(this); + } + + @Override + public Class getType() { + return SensorExecutionHandler.class; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/SensorsExecutor.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/SensorsExecutor.java new file mode 100644 index 00000000000..3af1cf2b6ee --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/SensorsExecutor.java @@ -0,0 +1,61 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.phases; + +import com.google.common.collect.Lists; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.Sensor; +import org.sonar.api.batch.SensorContext; +import org.sonar.api.resources.Project; +import org.sonar.batch.bootstrap.BatchExtensionDictionnary; +import org.sonar.batch.events.EventBus; + +import java.util.Collection; + +@BatchSide +public class SensorsExecutor { + + private EventBus eventBus; + private Project module; + private BatchExtensionDictionnary selector; + + public SensorsExecutor(BatchExtensionDictionnary selector, Project project, EventBus eventBus) { + this.selector = selector; + this.eventBus = eventBus; + this.module = project; + } + + public void execute(SensorContext context) { + Collection<Sensor> sensors = selector.select(Sensor.class, module, true, null); + eventBus.fireEvent(new SensorsPhaseEvent(Lists.newArrayList(sensors), true)); + + for (Sensor sensor : sensors) { + executeSensor(context, sensor); + } + + eventBus.fireEvent(new SensorsPhaseEvent(Lists.newArrayList(sensors), false)); + } + + private void executeSensor(SensorContext context, Sensor sensor) { + eventBus.fireEvent(new SensorExecutionEvent(sensor, true)); + sensor.analyse(module, context); + eventBus.fireEvent(new SensorExecutionEvent(sensor, false)); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/SensorsPhaseEvent.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/SensorsPhaseEvent.java new file mode 100644 index 00000000000..a1f658efa1e --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/SensorsPhaseEvent.java @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.phases; + +import org.sonar.api.batch.Sensor; +import org.sonar.api.batch.events.SensorsPhaseHandler; + +import java.util.List; + +class SensorsPhaseEvent extends AbstractPhaseEvent<SensorsPhaseHandler> + implements org.sonar.api.batch.events.SensorsPhaseHandler.SensorsPhaseEvent { + + private final List<Sensor> sensors; + + SensorsPhaseEvent(List<Sensor> sensors, boolean start) { + super(start); + this.sensors = sensors; + } + + @Override + public List<Sensor> getSensors() { + return sensors; + } + + @Override + protected void dispatch(SensorsPhaseHandler handler) { + handler.onSensorsPhase(this); + } + + @Override + protected Class getType() { + return SensorsPhaseHandler.class; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/package-info.java new file mode 100644 index 00000000000..f4cd97fca3c --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/phases/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.phases; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/platform/DefaultServer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/platform/DefaultServer.java new file mode 100644 index 00000000000..3136f4a3112 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/platform/DefaultServer.java @@ -0,0 +1,110 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.platform; + +import java.io.File; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import javax.annotation.CheckForNull; +import org.apache.commons.lang.StringUtils; +import org.slf4j.LoggerFactory; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.config.Settings; +import org.sonar.api.platform.Server; +import org.sonar.batch.bootstrap.GlobalProperties; + +@BatchSide +public class DefaultServer extends Server { + + private Settings settings; + private GlobalProperties props; + + public DefaultServer(Settings settings, GlobalProperties props) { + this.settings = settings; + this.props = props; + } + + @Override + public String getId() { + return settings.getString(CoreProperties.SERVER_ID); + } + + @Override + public String getVersion() { + return settings.getString(CoreProperties.SERVER_VERSION); + } + + @Override + public Date getStartedAt() { + String dateString = settings.getString(CoreProperties.SERVER_STARTTIME); + if (dateString != null) { + try { + return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(dateString); + + } catch (ParseException e) { + LoggerFactory.getLogger(getClass()).error("The property " + CoreProperties.SERVER_STARTTIME + " is badly formatted.", e); + } + } + return null; + } + + @Override + public File getRootDir() { + return null; + } + + @Override + @CheckForNull + public File getDeployDir() { + return null; + } + + @Override + public String getContextPath() { + return ""; + } + + @Override + public String getPublicRootUrl() { + return null; + } + + @Override + public boolean isDev() { + return false; + } + + @Override + public boolean isSecured() { + return false; + } + + @Override + public String getURL() { + return StringUtils.removeEnd(StringUtils.defaultIfBlank(props.property("sonar.host.url"), "http://localhost:9000"), "/"); + } + + @Override + public String getPermanentServerId() { + return settings.getString(CoreProperties.PERMANENT_SERVER_ID); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/platform/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/platform/package-info.java new file mode 100644 index 00000000000..cb2793702b0 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/platform/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.platform; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/postjob/DefaultPostJobContext.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/postjob/DefaultPostJobContext.java new file mode 100644 index 00000000000..02b1582b999 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/postjob/DefaultPostJobContext.java @@ -0,0 +1,154 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.postjob; + +import org.sonar.batch.issue.tracking.TrackedIssue; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; + +import javax.annotation.Nullable; + +import org.sonar.api.batch.AnalysisMode; +import org.sonar.api.batch.fs.InputComponent; +import org.sonar.api.batch.postjob.PostJobContext; +import org.sonar.api.batch.postjob.issue.Issue; +import org.sonar.api.batch.rule.Severity; +import org.sonar.api.config.Settings; +import org.sonar.api.rule.RuleKey; +import org.sonar.batch.index.BatchComponent; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.issue.IssueCache; + +public class DefaultPostJobContext implements PostJobContext { + + private final Settings settings; + private final AnalysisMode analysisMode; + private final IssueCache cache; + private final BatchComponentCache resourceCache; + + public DefaultPostJobContext(Settings settings, AnalysisMode analysisMode, IssueCache cache, BatchComponentCache resourceCache) { + this.settings = settings; + this.analysisMode = analysisMode; + this.cache = cache; + this.resourceCache = resourceCache; + } + + @Override + public Settings settings() { + return settings; + } + + @Override + public AnalysisMode analysisMode() { + return analysisMode; + } + + @Override + public Iterable<Issue> issues() { + return Iterables.transform(Iterables.filter(cache.all(), new ResolvedPredicate(false)), new IssueTransformer()); + } + + @Override + public Iterable<Issue> resolvedIssues() { + return Iterables.transform(Iterables.filter(cache.all(), new ResolvedPredicate(true)), new IssueTransformer()); + } + + private class DefaultIssueWrapper implements Issue { + + private final TrackedIssue wrapped; + + public DefaultIssueWrapper(TrackedIssue wrapped) { + this.wrapped = wrapped; + } + + @Override + public String key() { + return wrapped.key(); + } + + @Override + public RuleKey ruleKey() { + return wrapped.getRuleKey(); + } + + @Override + public String componentKey() { + return wrapped.componentKey(); + } + + @Override + public InputComponent inputComponent() { + BatchComponent component = resourceCache.get(wrapped.componentKey()); + return component != null ? component.inputComponent() : null; + } + + @Override + public Integer line() { + return wrapped.startLine(); + } + + @Override + public Double effortToFix() { + return wrapped.gap(); + } + + @Override + public String message() { + return wrapped.getMessage(); + } + + @Override + public Severity severity() { + String severity = wrapped.severity(); + return severity != null ? Severity.valueOf(severity) : null; + } + + @Override + public boolean isNew() { + return wrapped.isNew(); + } + } + + private class IssueTransformer implements Function<TrackedIssue, Issue> { + @Override + public Issue apply(TrackedIssue input) { + return new DefaultIssueWrapper(input); + } + } + + private static class ResolvedPredicate implements Predicate<TrackedIssue> { + private final boolean resolved; + + private ResolvedPredicate(boolean resolved) { + this.resolved = resolved; + } + + @Override + public boolean apply(@Nullable TrackedIssue issue) { + if (issue != null) { + return resolved ? issue.resolution() != null : issue.resolution() == null; + } + return false; + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/postjob/PostJobOptimizer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/postjob/PostJobOptimizer.java new file mode 100644 index 00000000000..b27e89e87f8 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/postjob/PostJobOptimizer.java @@ -0,0 +1,68 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.postjob; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.api.batch.postjob.internal.DefaultPostJobDescriptor; +import org.sonar.api.config.Settings; + +@BatchSide +public class PostJobOptimizer { + + private static final Logger LOG = LoggerFactory.getLogger(PostJobOptimizer.class); + + private final Settings settings; + private final AnalysisMode analysisMode; + + public PostJobOptimizer(Settings settings, AnalysisMode analysisMode) { + this.settings = settings; + this.analysisMode = analysisMode; + } + + /** + * Decide if the given PostJob should be executed. + */ + public boolean shouldExecute(DefaultPostJobDescriptor descriptor) { + if (!settingsCondition(descriptor)) { + LOG.debug("'{}' skipped because one of the required properties is missing", descriptor.name()); + return false; + } + if (descriptor.isDisabledInIssues() && analysisMode.isIssues()) { + LOG.debug("'{}' skipped in issues mode", descriptor.name()); + return false; + } + return true; + } + + private boolean settingsCondition(DefaultPostJobDescriptor descriptor) { + if (!descriptor.properties().isEmpty()) { + for (String propertyKey : descriptor.properties()) { + if (!settings.hasKey(propertyKey)) { + return false; + } + } + } + return true; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/postjob/PostJobWrapper.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/postjob/PostJobWrapper.java new file mode 100644 index 00000000000..c5ba661637c --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/postjob/PostJobWrapper.java @@ -0,0 +1,65 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.postjob; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.CheckProject; +import org.sonar.api.batch.postjob.PostJob; +import org.sonar.api.batch.postjob.PostJobContext; +import org.sonar.api.batch.postjob.internal.DefaultPostJobDescriptor; +import org.sonar.api.resources.Project; + +public class PostJobWrapper implements org.sonar.api.batch.PostJob, CheckProject { + + private static final Logger LOG = LoggerFactory.getLogger(PostJobWrapper.class); + + private PostJob wrappedPostJob; + private PostJobContext adaptor; + private DefaultPostJobDescriptor descriptor; + private PostJobOptimizer optimizer; + + public PostJobWrapper(PostJob newPostJob, PostJobContext adaptor, PostJobOptimizer optimizer) { + this.wrappedPostJob = newPostJob; + this.optimizer = optimizer; + this.descriptor = new DefaultPostJobDescriptor(); + newPostJob.describe(descriptor); + this.adaptor = adaptor; + } + + public PostJob wrappedPostJob() { + return wrappedPostJob; + } + + @Override + public boolean shouldExecuteOnProject(Project project) { + return optimizer.shouldExecute(descriptor); + } + + @Override + public void executeOn(Project project, org.sonar.api.batch.SensorContext context) { + wrappedPostJob.execute(adaptor); + } + + @Override + public String toString() { + return descriptor.name() + (LOG.isDebugEnabled() ? " (wrapped)" : ""); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/postjob/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/postjob/package-info.java new file mode 100644 index 00000000000..0e8cb6ac72d --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/postjob/package-info.java @@ -0,0 +1,21 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@javax.annotation.ParametersAreNonnullByDefault +package org.sonar.batch.postjob; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/profiling/AbstractTimeProfiling.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/profiling/AbstractTimeProfiling.java new file mode 100644 index 00000000000..6e204596616 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/profiling/AbstractTimeProfiling.java @@ -0,0 +1,116 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.profiling; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.TimeUtils; + +public abstract class AbstractTimeProfiling { + + private final long startTime; + + private long totalTime; + + private System2 system; + + public AbstractTimeProfiling(System2 system) { + this.system = system; + this.startTime = system.now(); + } + + protected System2 system() { + return system; + } + + public long startTime() { + return startTime; + } + + public void stop() { + this.totalTime = system.now() - startTime; + } + + public long totalTime() { + return totalTime; + } + + public String totalTimeAsString() { + return TimeUtils.formatDuration(totalTime); + } + + public void setTotalTime(long totalTime) { + this.totalTime = totalTime; + } + + protected void add(AbstractTimeProfiling other) { + this.setTotalTime(this.totalTime() + other.totalTime()); + } + + static <G extends AbstractTimeProfiling> Map<Object, G> sortByDescendingTotalTime(Map<?, G> unsorted) { + List<Map.Entry<?, G>> entries = + new ArrayList<Map.Entry<?, G>>(unsorted.entrySet()); + Collections.sort(entries, new Comparator<Map.Entry<?, G>>() { + @Override + public int compare(Map.Entry<?, G> o1, Map.Entry<?, G> o2) { + return Long.valueOf(o2.getValue().totalTime()).compareTo(o1.getValue().totalTime()); + } + }); + Map<Object, G> sortedMap = new LinkedHashMap<>(); + for (Map.Entry<?, G> entry : entries) { + sortedMap.put(entry.getKey(), entry.getValue()); + } + return sortedMap; + } + + static <G extends AbstractTimeProfiling> List<G> truncate(Collection<G> sortedList) { + int maxSize = 10; + List<G> result = new ArrayList<>(maxSize); + int i = 0; + for (G item : sortedList) { + if (i >= maxSize || item.totalTime() == 0) { + return result; + } + i++; + result.add(item); + } + return result; + } + + protected void println(String msg) { + PhasesSumUpTimeProfiler.println(msg); + } + + protected void println(String text, @Nullable Double percent, AbstractTimeProfiling phaseProfiling) { + PhasesSumUpTimeProfiler.println(text, percent, phaseProfiling); + } + + protected void println(String text, AbstractTimeProfiling phaseProfiling) { + println(text, null, phaseProfiling); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/profiling/ItemProfiling.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/profiling/ItemProfiling.java new file mode 100644 index 00000000000..abbb11ccbcb --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/profiling/ItemProfiling.java @@ -0,0 +1,37 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.profiling; + +import org.sonar.api.utils.System2; + +public class ItemProfiling extends AbstractTimeProfiling { + + private final String itemName; + + public ItemProfiling(System2 system, String itemName) { + super(system); + this.itemName = itemName; + } + + public String itemName() { + return itemName; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/profiling/ModuleProfiling.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/profiling/ModuleProfiling.java new file mode 100644 index 00000000000..7c04dcff55e --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/profiling/ModuleProfiling.java @@ -0,0 +1,105 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.profiling; + +import com.google.common.collect.Maps; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import javax.annotation.Nullable; +import org.sonar.api.resources.Project; +import org.sonar.api.utils.System2; + +public class ModuleProfiling extends AbstractTimeProfiling { + + private Map<Phase, PhaseProfiling> profilingPerPhase = new HashMap<>(); + private Map<String, ItemProfiling> profilingPerBatchStep = new LinkedHashMap<>(); + private final Project module; + + public ModuleProfiling(@Nullable Project module, System2 system) { + super(system); + this.module = module; + } + + public String moduleName() { + if (module != null) { + return module.getName(); + } + return null; + } + + public PhaseProfiling getProfilingPerPhase(Phase phase) { + return profilingPerPhase.get(phase); + } + + public ItemProfiling getProfilingPerBatchStep(String stepName) { + return profilingPerBatchStep.get(stepName); + } + + public void addPhaseProfiling(Phase phase) { + profilingPerPhase.put(phase, PhaseProfiling.create(system(), phase)); + } + + public void addBatchStepProfiling(String stepName) { + profilingPerBatchStep.put(stepName, new ItemProfiling(system(), stepName)); + } + + public void dump(Properties props) { + double percent = this.totalTime() / 100.0; + Map<Object, AbstractTimeProfiling> categories = Maps.newLinkedHashMap(); + categories.putAll(profilingPerPhase); + categories.putAll(profilingPerBatchStep); + + for (Map.Entry<Object, AbstractTimeProfiling> batchStep : categories.entrySet()) { + props.setProperty(batchStep.getKey().toString(), Long.toString(batchStep.getValue().totalTime())); + } + + for (Map.Entry<Object, AbstractTimeProfiling> batchStep : sortByDescendingTotalTime(categories).entrySet()) { + println(" * " + batchStep.getKey() + " execution time: ", percent, batchStep.getValue()); + } + // Breakdown per phase + for (Phase phase : Phase.values()) { + if (profilingPerPhase.containsKey(phase) && getProfilingPerPhase(phase).hasItems()) { + println(""); + println(" * " + phase + " execution time breakdown: ", getProfilingPerPhase(phase)); + getProfilingPerPhase(phase).dump(props); + } + } + } + + public void merge(ModuleProfiling other) { + super.add(other); + for (Entry<Phase, PhaseProfiling> entry : other.profilingPerPhase.entrySet()) { + if (!this.profilingPerPhase.containsKey(entry.getKey())) { + this.addPhaseProfiling(entry.getKey()); + } + this.getProfilingPerPhase(entry.getKey()).merge(entry.getValue()); + } + for (Map.Entry<String, ItemProfiling> entry : other.profilingPerBatchStep.entrySet()) { + if (!this.profilingPerBatchStep.containsKey(entry.getKey())) { + profilingPerBatchStep.put(entry.getKey(), new ItemProfiling(system(), entry.getKey())); + } + this.getProfilingPerBatchStep(entry.getKey()).add(entry.getValue()); + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/profiling/Phase.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/profiling/Phase.java new file mode 100644 index 00000000000..f00496f9644 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/profiling/Phase.java @@ -0,0 +1,37 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.profiling; + +public enum Phase { + + INIT("Initializers"), SENSOR("Sensors"), DECORATOR("Decorators"), PERSISTER("Persisters"), POSTJOB("Post-Jobs"); + + private final String label; + + private Phase(String label) { + this.label = label; + } + + @Override + public String toString() { + return label; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/profiling/PhaseProfiling.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/profiling/PhaseProfiling.java new file mode 100644 index 00000000000..5b13f1d4ee1 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/profiling/PhaseProfiling.java @@ -0,0 +1,98 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.profiling; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import org.sonar.api.utils.System2; + +public class PhaseProfiling extends AbstractTimeProfiling { + + private final Phase phase; + + private Map<String, ItemProfiling> profilingPerItem = new HashMap<>(); + + PhaseProfiling(System2 system, Phase phase) { + super(system); + this.phase = phase; + } + + public static PhaseProfiling create(System2 system, Phase phase) { + return new PhaseProfiling(system, phase); + } + + public Phase phase() { + return phase; + } + + public boolean hasItems() { + return !profilingPerItem.isEmpty(); + } + + public ItemProfiling getProfilingPerItem(Object item) { + String stringOrSimpleName = toStringOrSimpleName(item); + return profilingPerItem.get(stringOrSimpleName); + } + + public void newItemProfiling(Object item) { + String stringOrSimpleName = toStringOrSimpleName(item); + profilingPerItem.put(stringOrSimpleName, new ItemProfiling(system(), stringOrSimpleName)); + } + + public void newItemProfiling(String itemName) { + profilingPerItem.put(itemName, new ItemProfiling(system(), itemName)); + } + + public void merge(PhaseProfiling other) { + super.add(other); + for (Entry<String, ItemProfiling> entry : other.profilingPerItem.entrySet()) { + if (!this.profilingPerItem.containsKey(entry.getKey())) { + newItemProfiling(entry.getKey()); + } + this.getProfilingPerItem(entry.getKey()).add(entry.getValue()); + } + } + + public void dump(Properties props) { + double percent = this.totalTime() / 100.0; + for (ItemProfiling itemProfiling : profilingPerItem.values()) { + props.setProperty(itemProfiling.itemName(), Long.toString(itemProfiling.totalTime())); + } + for (ItemProfiling itemProfiling : truncate(sortByDescendingTotalTime(profilingPerItem).values())) { + println(" o " + itemProfiling.itemName() + ": ", percent, itemProfiling); + } + } + + /** + * Try to use toString if it is not the default {@link Object#toString()}. Else use {@link Class#getSimpleName()} + * @param o + * @return + */ + private static String toStringOrSimpleName(Object o) { + String toString = o.toString(); + if (toString == null || toString.startsWith(o.getClass().getName())) { + return o.getClass().getSimpleName(); + } + return toString; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/profiling/PhasesSumUpTimeProfiler.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/profiling/PhasesSumUpTimeProfiler.java new file mode 100644 index 00000000000..f03b84caaac --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/profiling/PhasesSumUpTimeProfiler.java @@ -0,0 +1,278 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.profiling; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Lists; +import java.io.File; +import java.io.FileOutputStream; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import javax.annotation.Nullable; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.Decorator; +import org.sonar.api.batch.events.DecoratorExecutionHandler; +import org.sonar.api.batch.events.DecoratorsPhaseHandler; +import org.sonar.api.batch.events.InitializerExecutionHandler; +import org.sonar.api.batch.events.InitializersPhaseHandler; +import org.sonar.api.batch.events.PostJobExecutionHandler; +import org.sonar.api.batch.events.PostJobsPhaseHandler; +import org.sonar.api.batch.events.ProjectAnalysisHandler; +import org.sonar.api.batch.events.SensorExecutionHandler; +import org.sonar.api.batch.events.SensorsPhaseHandler; +import org.sonar.api.resources.Project; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.TimeUtils; +import org.sonar.batch.bootstrap.GlobalProperties; +import org.sonar.batch.events.BatchStepHandler; +import org.sonar.batch.util.BatchUtils; + +import static org.sonar.batch.profiling.AbstractTimeProfiling.sortByDescendingTotalTime; +import static org.sonar.batch.profiling.AbstractTimeProfiling.truncate; + +public class PhasesSumUpTimeProfiler implements ProjectAnalysisHandler, SensorExecutionHandler, DecoratorExecutionHandler, PostJobExecutionHandler, DecoratorsPhaseHandler, + SensorsPhaseHandler, PostJobsPhaseHandler, InitializersPhaseHandler, InitializerExecutionHandler, BatchStepHandler { + + static final Logger LOG = LoggerFactory.getLogger(PhasesSumUpTimeProfiler.class); + private static final int TEXT_RIGHT_PAD = 60; + private static final int TIME_LEFT_PAD = 10; + + @VisibleForTesting + ModuleProfiling currentModuleProfiling; + + @VisibleForTesting + ModuleProfiling totalProfiling; + + private Map<Project, ModuleProfiling> modulesProfilings = new HashMap<>(); + private DecoratorsProfiler decoratorsProfiler; + + private final System2 system; + private final File out; + + public PhasesSumUpTimeProfiler(System2 system, GlobalProperties bootstrapProps) { + String workingDirPath = StringUtils.defaultIfBlank(bootstrapProps.property(CoreProperties.WORKING_DIRECTORY), CoreProperties.WORKING_DIRECTORY_DEFAULT_VALUE); + File workingDir = new File(workingDirPath).getAbsoluteFile(); + this.out = new File(workingDir, "profiling"); + this.out.mkdirs(); + this.totalProfiling = new ModuleProfiling(null, system); + this.system = system; + } + + static void println(String msg) { + LOG.info(msg); + } + + static void println(String text, @Nullable Double percent, AbstractTimeProfiling phaseProfiling) { + StringBuilder sb = new StringBuilder(); + sb.append(StringUtils.rightPad(text, TEXT_RIGHT_PAD)).append(StringUtils.leftPad(phaseProfiling.totalTimeAsString(), TIME_LEFT_PAD)); + if (percent != null) { + sb.append(" (").append((int) (phaseProfiling.totalTime() / percent)).append("%)"); + } + println(sb.toString()); + } + + @Override + public void onProjectAnalysis(ProjectAnalysisEvent event) { + Project module = event.getProject(); + if (event.isStart()) { + decoratorsProfiler = new DecoratorsProfiler(); + currentModuleProfiling = new ModuleProfiling(module, system); + } else { + currentModuleProfiling.stop(); + modulesProfilings.put(module, currentModuleProfiling); + long moduleTotalTime = currentModuleProfiling.totalTime(); + println(""); + println(" -------- Profiling of module " + module.getName() + ": " + TimeUtils.formatDuration(moduleTotalTime) + " --------"); + println(""); + Properties props = new Properties(); + currentModuleProfiling.dump(props); + println(""); + println(" -------- End of profiling of module " + module.getName() + " --------"); + println(""); + String fileName = module.getKey() + "-profiler.properties"; + dumpToFile(props, BatchUtils.cleanKeyForFilename(fileName)); + totalProfiling.merge(currentModuleProfiling); + if (module.isRoot() && !module.getModules().isEmpty()) { + dumpTotalExecutionSummary(); + } + } + } + + private void dumpTotalExecutionSummary() { + totalProfiling.stop(); + long totalTime = totalProfiling.totalTime(); + println(""); + println(" ======== Profiling of total execution: " + TimeUtils.formatDuration(totalTime) + " ========"); + println(""); + println(" * Module execution time breakdown: "); + double percent = totalTime / 100.0; + for (ModuleProfiling modulesProfiling : truncate(sortByDescendingTotalTime(modulesProfilings).values())) { + println(" o " + modulesProfiling.moduleName() + " execution time: ", percent, modulesProfiling); + } + println(""); + Properties props = new Properties(); + totalProfiling.dump(props); + println(""); + println(" ======== End of profiling of total execution ========"); + println(""); + String fileName = "total-execution-profiler.properties"; + dumpToFile(props, fileName); + } + + private void dumpToFile(Properties props, String fileName) { + File file = new File(out, fileName); + try (FileOutputStream fos = new FileOutputStream(file)) { + props.store(fos, "SonarQube"); + println("Profiling data stored in " + file.getAbsolutePath()); + } catch (Exception e) { + throw new IllegalStateException("Unable to store profiler output: " + file, e); + } + } + + @Override + public void onSensorsPhase(SensorsPhaseEvent event) { + if (event.isStart()) { + currentModuleProfiling.addPhaseProfiling(Phase.SENSOR); + } else { + currentModuleProfiling.getProfilingPerPhase(Phase.SENSOR).stop(); + } + } + + @Override + public void onSensorExecution(SensorExecutionEvent event) { + PhaseProfiling profiling = currentModuleProfiling.getProfilingPerPhase(Phase.SENSOR); + if (event.isStart()) { + profiling.newItemProfiling(event.getSensor()); + } else { + profiling.getProfilingPerItem(event.getSensor()).stop(); + } + } + + @Override + public void onDecoratorExecution(DecoratorExecutionEvent event) { + PhaseProfiling profiling = currentModuleProfiling.getProfilingPerPhase(Phase.DECORATOR); + if (event.isStart()) { + if (profiling.getProfilingPerItem(event.getDecorator()) == null) { + profiling.newItemProfiling(event.getDecorator()); + } + decoratorsProfiler.start(event.getDecorator()); + } else { + decoratorsProfiler.stop(); + } + } + + @Override + public void onDecoratorsPhase(DecoratorsPhaseEvent event) { + if (event.isStart()) { + currentModuleProfiling.addPhaseProfiling(Phase.DECORATOR); + } else { + for (Decorator decorator : decoratorsProfiler.getDurations().keySet()) { + currentModuleProfiling.getProfilingPerPhase(Phase.DECORATOR) + .getProfilingPerItem(decorator).setTotalTime(decoratorsProfiler.getDurations().get(decorator)); + } + currentModuleProfiling.getProfilingPerPhase(Phase.DECORATOR).stop(); + } + } + + @Override + public void onPostJobsPhase(PostJobsPhaseEvent event) { + if (event.isStart()) { + currentModuleProfiling.addPhaseProfiling(Phase.POSTJOB); + } else { + currentModuleProfiling.getProfilingPerPhase(Phase.POSTJOB).stop(); + } + } + + @Override + public void onPostJobExecution(PostJobExecutionEvent event) { + PhaseProfiling profiling = currentModuleProfiling.getProfilingPerPhase(Phase.POSTJOB); + if (event.isStart()) { + profiling.newItemProfiling(event.getPostJob()); + } else { + profiling.getProfilingPerItem(event.getPostJob()).stop(); + } + } + + @Override + public void onInitializersPhase(InitializersPhaseEvent event) { + if (event.isStart()) { + currentModuleProfiling.addPhaseProfiling(Phase.INIT); + } else { + currentModuleProfiling.getProfilingPerPhase(Phase.INIT).stop(); + } + } + + @Override + public void onInitializerExecution(InitializerExecutionEvent event) { + PhaseProfiling profiling = currentModuleProfiling.getProfilingPerPhase(Phase.INIT); + if (event.isStart()) { + profiling.newItemProfiling(event.getInitializer()); + } else { + profiling.getProfilingPerItem(event.getInitializer()).stop(); + } + } + + @Override + public void onBatchStep(BatchStepEvent event) { + if (event.isStart()) { + currentModuleProfiling.addBatchStepProfiling(event.stepName()); + } else { + currentModuleProfiling.getProfilingPerBatchStep(event.stepName()).stop(); + } + } + + class DecoratorsProfiler { + private List<Decorator> decorators = Lists.newArrayList(); + private Map<Decorator, Long> durations = new IdentityHashMap<>(); + private long startTime; + private Decorator currentDecorator; + + DecoratorsProfiler() { + } + + void start(Decorator decorator) { + this.startTime = system.now(); + this.currentDecorator = decorator; + } + + void stop() { + final Long cumulatedDuration; + if (durations.containsKey(currentDecorator)) { + cumulatedDuration = durations.get(currentDecorator); + } else { + decorators.add(currentDecorator); + cumulatedDuration = 0L; + } + durations.put(currentDecorator, cumulatedDuration + (system.now() - startTime)); + } + + public Map<Decorator, Long> getDurations() { + return durations; + } + + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/profiling/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/profiling/package-info.java new file mode 100644 index 00000000000..8f57dd4e4c7 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/profiling/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.profiling; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/report/ActiveRulesPublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/report/ActiveRulesPublisher.java new file mode 100644 index 00000000000..ba5dd00fbac --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/report/ActiveRulesPublisher.java @@ -0,0 +1,62 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.report; + +import com.google.common.base.Function; +import com.google.common.collect.FluentIterable; +import java.util.Map; +import javax.annotation.Nonnull; +import org.sonar.api.batch.rule.ActiveRule; +import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.scanner.protocol.Constants; +import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.scanner.protocol.output.ScannerReportWriter; + +public class ActiveRulesPublisher implements ReportPublisherStep { + + private final ActiveRules activeRules; + + public ActiveRulesPublisher(ActiveRules activeRules) { + this.activeRules = activeRules; + } + + @Override + public void publish(ScannerReportWriter writer) { + Iterable<ScannerReport.ActiveRule> activeRuleMessages = FluentIterable.from(activeRules.findAll()).transform(new ToMessage()); + writer.writeActiveRules(activeRuleMessages); + } + + private static class ToMessage implements Function<ActiveRule, ScannerReport.ActiveRule> { + private final ScannerReport.ActiveRule.Builder builder = ScannerReport.ActiveRule.newBuilder(); + + @Override + public ScannerReport.ActiveRule apply(@Nonnull ActiveRule input) { + builder.clear(); + builder.setRuleRepository(input.ruleKey().repository()); + builder.setRuleKey(input.ruleKey().rule()); + builder.setSeverity(Constants.Severity.valueOf(input.severity())); + for (Map.Entry<String, String> entry : input.params().entrySet()) { + builder.addParamBuilder().setKey(entry.getKey()).setValue(entry.getValue()).build(); + + } + return builder.build(); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/report/AnalysisContextReportPublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/report/AnalysisContextReportPublisher.java new file mode 100644 index 00000000000..614e80d71a4 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/report/AnalysisContextReportPublisher.java @@ -0,0 +1,164 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.report; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.TreeSet; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.batch.bootstrap.BatchPluginRepository; +import org.sonar.batch.repository.ProjectRepositories; +import org.sonar.core.platform.PluginInfo; +import org.sonar.scanner.protocol.output.ScannerReportWriter; + +@BatchSide +public class AnalysisContextReportPublisher { + + private static final Logger LOG = Loggers.get(AnalysisContextReportPublisher.class); + + private static final String ENV_PROP_PREFIX = "env."; + private static final String SONAR_PROP_PREFIX = "sonar."; + private final BatchPluginRepository pluginRepo; + private final AnalysisMode mode; + private final System2 system; + private final ProjectRepositories projectRepos; + + private ScannerReportWriter writer; + + public AnalysisContextReportPublisher(AnalysisMode mode, BatchPluginRepository pluginRepo, System2 system, ProjectRepositories projectRepos) { + this.mode = mode; + this.pluginRepo = pluginRepo; + this.system = system; + this.projectRepos = projectRepos; + } + + public void init(ScannerReportWriter writer) { + if (mode.isIssues()) { + return; + } + this.writer = writer; + File analysisLog = writer.getFileStructure().analysisLog(); + try (BufferedWriter fileWriter = Files.newBufferedWriter(analysisLog.toPath(), StandardCharsets.UTF_8)) { + if (LOG.isDebugEnabled()) { + writeEnvVariables(fileWriter); + writeSystemProps(fileWriter); + } + writePlugins(fileWriter); + } catch (IOException e) { + throw new IllegalStateException("Unable to write analysis log", e); + } + } + + private void writePlugins(BufferedWriter fileWriter) throws IOException { + fileWriter.write("SonarQube plugins:\n"); + for (PluginInfo p : pluginRepo.getPluginInfos()) { + fileWriter.append(String.format(" - %s %s (%s)", p.getName(), p.getVersion(), p.getKey())).append('\n'); + } + } + + private void writeSystemProps(BufferedWriter fileWriter) throws IOException { + fileWriter.write("System properties:\n"); + Properties sysProps = system.properties(); + for (String prop : new TreeSet<>(sysProps.stringPropertyNames())) { + if (prop.startsWith(SONAR_PROP_PREFIX)) { + continue; + } + fileWriter.append(String.format(" - %s=%s", prop, sysProps.getProperty(prop))).append('\n'); + } + } + + private void writeEnvVariables(BufferedWriter fileWriter) throws IOException { + fileWriter.append("Environment variables:\n"); + Map<String, String> envVariables = system.envVariables(); + for (String env : new TreeSet<>(envVariables.keySet())) { + fileWriter.append(String.format(" - %s=%s", env, envVariables.get(env))).append('\n'); + } + } + + public void dumpSettings(ProjectDefinition moduleDefinition) { + if (mode.isIssues()) { + return; + } + + File analysisLog = writer.getFileStructure().analysisLog(); + try (BufferedWriter fileWriter = Files.newBufferedWriter(analysisLog.toPath(), StandardCharsets.UTF_8, StandardOpenOption.WRITE, StandardOpenOption.APPEND)) { + Map<String, String> moduleSpecificProps = collectModuleSpecificProps(moduleDefinition); + fileWriter.append(String.format("Settings for module: %s", moduleDefinition.getKey())).append('\n'); + for (String prop : new TreeSet<>(moduleSpecificProps.keySet())) { + if (isSystemProp(prop) || isEnvVariable(prop) || !isSqProp(prop)) { + continue; + } + fileWriter.append(String.format(" - %s=%s", prop, sensitive(prop) ? "******" : moduleSpecificProps.get(prop))).append('\n'); + } + } catch (IOException e) { + throw new IllegalStateException("Unable to write analysis log", e); + } + } + + /** + * Only keep props that are not in parent + */ + private Map<String, String> collectModuleSpecificProps(ProjectDefinition moduleDefinition) { + Map<String, String> moduleSpecificProps = new HashMap<>(); + if (projectRepos.moduleExists(moduleDefinition.getKeyWithBranch())) { + moduleSpecificProps.putAll(projectRepos.settings(moduleDefinition.getKeyWithBranch())); + } + ProjectDefinition parent = moduleDefinition.getParent(); + if (parent == null) { + moduleSpecificProps.putAll(moduleDefinition.properties()); + } else { + Map<String, String> parentProps = parent.properties(); + for (Map.Entry<String, String> entry : moduleDefinition.properties().entrySet()) { + if (!parentProps.containsKey(entry.getKey()) || !parentProps.get(entry.getKey()).equals(entry.getValue())) { + moduleSpecificProps.put(entry.getKey(), entry.getValue()); + } + } + } + return moduleSpecificProps; + } + + private static boolean isSqProp(String propKey) { + return propKey.startsWith(SONAR_PROP_PREFIX); + } + + private boolean isSystemProp(String propKey) { + return system.properties().containsKey(propKey) && !propKey.startsWith(SONAR_PROP_PREFIX); + } + + private boolean isEnvVariable(String propKey) { + return propKey.startsWith(ENV_PROP_PREFIX) && system.envVariables().containsKey(propKey.substring(ENV_PROP_PREFIX.length())); + } + + private static boolean sensitive(String key) { + return key.contains(".password") || key.contains(".secured"); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/report/ComponentsPublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/report/ComponentsPublisher.java new file mode 100644 index 00000000000..0aa66d0bdca --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/report/ComponentsPublisher.java @@ -0,0 +1,175 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.report; + +import javax.annotation.CheckForNull; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.resources.Language; +import org.sonar.api.resources.Resource; +import org.sonar.api.resources.ResourceUtils; +import org.sonar.batch.index.BatchComponent; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.scan.ImmutableProjectReactor; +import org.sonar.scanner.protocol.Constants; +import org.sonar.scanner.protocol.Constants.ComponentLinkType; +import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.scanner.protocol.output.ScannerReportWriter; +import org.sonar.scanner.protocol.output.ScannerReport.ComponentLink; + +/** + * Adds components and analysis metadata to output report + */ +public class ComponentsPublisher implements ReportPublisherStep { + + private final BatchComponentCache resourceCache; + private final ImmutableProjectReactor reactor; + + public ComponentsPublisher(ImmutableProjectReactor reactor, BatchComponentCache resourceCache) { + this.reactor = reactor; + this.resourceCache = resourceCache; + } + + @Override + public void publish(ScannerReportWriter writer) { + BatchComponent rootProject = resourceCache.get(reactor.getRoot().getKeyWithBranch()); + recursiveWriteComponent(rootProject, writer); + } + + private void recursiveWriteComponent(BatchComponent batchComponent, ScannerReportWriter writer) { + Resource r = batchComponent.resource(); + ScannerReport.Component.Builder builder = ScannerReport.Component.newBuilder(); + + // non-null fields + builder.setRef(batchComponent.batchId()); + builder.setType(getType(r)); + + // Don't set key on directories and files to save space since it can be deduced from path + if (batchComponent.isProjectOrModule()) { + // Here we want key without branch + ProjectDefinition def = reactor.getProjectDefinition(batchComponent.key()); + builder.setKey(def.getKey()); + } + + // protocol buffers does not accept null values + + if (batchComponent.isFile()) { + builder.setIsTest(ResourceUtils.isUnitTestFile(r)); + builder.setLines(((InputFile) batchComponent.inputComponent()).lines()); + } + String name = getName(r); + if (name != null) { + builder.setName(name); + } + String description = getDescription(r); + if (description != null) { + builder.setDescription(description); + } + String path = r.getPath(); + if (path != null) { + builder.setPath(path); + } + String lang = getLanguageKey(r); + if (lang != null) { + builder.setLanguage(lang); + } + for (BatchComponent child : batchComponent.children()) { + builder.addChildRef(child.batchId()); + } + writeLinks(batchComponent, builder); + writeVersion(batchComponent, builder); + writer.writeComponent(builder.build()); + + for (BatchComponent child : batchComponent.children()) { + recursiveWriteComponent(child, writer); + } + } + + private void writeVersion(BatchComponent c, ScannerReport.Component.Builder builder) { + if (c.isProjectOrModule()) { + ProjectDefinition def = reactor.getProjectDefinition(c.key()); + String version = getVersion(def); + builder.setVersion(version); + } + } + + private static String getVersion(ProjectDefinition def) { + String version = def.getVersion(); + return StringUtils.isNotBlank(version) ? version : getVersion(def.getParent()); + } + + private void writeLinks(BatchComponent c, ScannerReport.Component.Builder builder) { + if (c.isProjectOrModule()) { + ProjectDefinition def = reactor.getProjectDefinition(c.key()); + ComponentLink.Builder linkBuilder = ComponentLink.newBuilder(); + + writeProjectLink(builder, def, linkBuilder, CoreProperties.LINKS_HOME_PAGE, ComponentLinkType.HOME); + writeProjectLink(builder, def, linkBuilder, CoreProperties.LINKS_CI, ComponentLinkType.CI); + writeProjectLink(builder, def, linkBuilder, CoreProperties.LINKS_ISSUE_TRACKER, ComponentLinkType.ISSUE); + writeProjectLink(builder, def, linkBuilder, CoreProperties.LINKS_SOURCES, ComponentLinkType.SCM); + writeProjectLink(builder, def, linkBuilder, CoreProperties.LINKS_SOURCES_DEV, ComponentLinkType.SCM_DEV); + } + } + + private static void writeProjectLink(ScannerReport.Component.Builder componentBuilder, ProjectDefinition def, ComponentLink.Builder linkBuilder, String linkProp, + ComponentLinkType linkType) { + String link = def.properties().get(linkProp); + if (StringUtils.isNotBlank(link)) { + linkBuilder.setType(linkType); + linkBuilder.setHref(link); + componentBuilder.addLink(linkBuilder.build()); + linkBuilder.clear(); + } + } + + @CheckForNull + private static String getLanguageKey(Resource r) { + Language language = r.getLanguage(); + return ResourceUtils.isFile(r) && language != null ? language.getKey() : null; + } + + @CheckForNull + private static String getName(Resource r) { + // Don't return name for directories and files since it can be guessed from the path + return (ResourceUtils.isFile(r) || ResourceUtils.isDirectory(r)) ? null : r.getName(); + } + + @CheckForNull + private static String getDescription(Resource r) { + // Only for projets and modules + return ResourceUtils.isProject(r) ? r.getDescription() : null; + } + + private Constants.ComponentType getType(Resource r) { + if (ResourceUtils.isFile(r)) { + return Constants.ComponentType.FILE; + } else if (ResourceUtils.isDirectory(r)) { + return Constants.ComponentType.DIRECTORY; + } else if (ResourceUtils.isModuleProject(r)) { + return Constants.ComponentType.MODULE; + } else if (ResourceUtils.isRootProject(r)) { + return Constants.ComponentType.PROJECT; + } + throw new IllegalArgumentException("Unknown resource type: " + r); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/report/CoveragePublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/report/CoveragePublisher.java new file mode 100644 index 00000000000..1d6b9db71ef --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/report/CoveragePublisher.java @@ -0,0 +1,139 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.report; + +import com.google.common.base.Function; +import com.google.common.collect.Iterables; +import java.util.LinkedHashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.utils.KeyValueFormat; +import org.sonar.batch.index.BatchComponent; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.scan.measure.MeasureCache; +import org.sonar.scanner.protocol.output.ScannerReportWriter; +import org.sonar.scanner.protocol.output.ScannerReport.Coverage; +import org.sonar.scanner.protocol.output.ScannerReport.Coverage.Builder; + +public class CoveragePublisher implements ReportPublisherStep { + + private final BatchComponentCache resourceCache; + private final MeasureCache measureCache; + + public CoveragePublisher(BatchComponentCache resourceCache, MeasureCache measureCache) { + this.resourceCache = resourceCache; + this.measureCache = measureCache; + } + + @Override + public void publish(ScannerReportWriter writer) { + for (final BatchComponent resource : resourceCache.all()) { + if (!resource.isFile()) { + continue; + } + Map<Integer, Coverage.Builder> coveragePerLine = new LinkedHashMap<>(); + + int lineCount = ((InputFile) resource.inputComponent()).lines(); + applyLineMeasure(resource.key(), lineCount, CoreMetrics.COVERAGE_LINE_HITS_DATA_KEY, coveragePerLine, + new MeasureOperation() { + @Override + public void apply(String value, Coverage.Builder builder) { + builder.setUtHits(Integer.parseInt(value) > 0); + } + }); + applyLineMeasure(resource.key(), lineCount, CoreMetrics.CONDITIONS_BY_LINE_KEY, coveragePerLine, + new MeasureOperation() { + @Override + public void apply(String value, Coverage.Builder builder) { + builder.setConditions(Integer.parseInt(value)); + } + }); + applyLineMeasure(resource.key(), lineCount, CoreMetrics.COVERED_CONDITIONS_BY_LINE_KEY, coveragePerLine, + new MeasureOperation() { + @Override + public void apply(String value, Coverage.Builder builder) { + builder.setUtCoveredConditions(Integer.parseInt(value)); + } + }); + applyLineMeasure(resource.key(), lineCount, CoreMetrics.IT_COVERAGE_LINE_HITS_DATA_KEY, coveragePerLine, + new MeasureOperation() { + @Override + public void apply(String value, Coverage.Builder builder) { + builder.setItHits(Integer.parseInt(value) > 0); + } + }); + applyLineMeasure(resource.key(), lineCount, CoreMetrics.IT_COVERED_CONDITIONS_BY_LINE_KEY, coveragePerLine, + new MeasureOperation() { + @Override + public void apply(String value, Coverage.Builder builder) { + builder.setItCoveredConditions(Integer.parseInt(value)); + } + }); + applyLineMeasure(resource.key(), lineCount, CoreMetrics.OVERALL_COVERED_CONDITIONS_BY_LINE_KEY, coveragePerLine, + new MeasureOperation() { + @Override + public void apply(String value, Coverage.Builder builder) { + builder.setOverallCoveredConditions(Integer.parseInt(value)); + } + }); + writer.writeComponentCoverage(resource.batchId(), Iterables.transform(coveragePerLine.values(), BuildCoverage.INSTANCE)); + } + } + + void applyLineMeasure(String inputFileKey, int lineCount, String metricKey, Map<Integer, Coverage.Builder> coveragePerLine, MeasureOperation op) { + Measure measure = measureCache.byMetric(inputFileKey, metricKey); + if (measure != null) { + Map<Integer, String> lineMeasures = KeyValueFormat.parseIntString((String) measure.value()); + for (Map.Entry<Integer, String> lineMeasure : lineMeasures.entrySet()) { + int lineIdx = lineMeasure.getKey(); + if (lineIdx <= lineCount) { + String value = lineMeasure.getValue(); + if (StringUtils.isNotEmpty(value)) { + Coverage.Builder coverageBuilder = coveragePerLine.get(lineIdx); + if (coverageBuilder == null) { + coverageBuilder = Coverage.newBuilder(); + coverageBuilder.setLine(lineIdx); + coveragePerLine.put(lineIdx, coverageBuilder); + } + op.apply(value, coverageBuilder); + } + } + } + } + } + + interface MeasureOperation { + void apply(String value, Coverage.Builder builder); + } + + private enum BuildCoverage implements Function<Coverage.Builder, Coverage> { + INSTANCE; + + @Override + public Coverage apply(@Nonnull Builder input) { + return input.build(); + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/report/MeasuresPublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/report/MeasuresPublisher.java new file mode 100644 index 00000000000..51e8867ddca --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/report/MeasuresPublisher.java @@ -0,0 +1,168 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.report; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import java.io.Serializable; +import java.util.Set; +import javax.annotation.Nonnull; +import org.sonar.api.batch.measure.Metric; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.Metric.ValueType; +import org.sonar.batch.index.BatchComponent; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.scan.measure.MeasureCache; +import org.sonar.core.metric.BatchMetrics; +import org.sonar.scanner.protocol.Constants.MeasureValueType; +import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.scanner.protocol.output.ScannerReportWriter; + +import static com.google.common.collect.Iterables.filter; +import static com.google.common.collect.Iterables.transform; +import static com.google.common.collect.Sets.newHashSet; + +public class MeasuresPublisher implements ReportPublisherStep { + + private static final class MeasureToReportMeasure implements Function<Measure, ScannerReport.Measure> { + private final BatchComponent resource; + private final ScannerReport.Measure.Builder builder = ScannerReport.Measure.newBuilder(); + + private MeasureToReportMeasure(BatchComponent resource) { + this.resource = resource; + } + + @Override + public ScannerReport.Measure apply(@Nonnull Measure input) { + validateMeasure(input, resource.key()); + return toReportMeasure(builder, input); + } + + private static void validateMeasure(Measure measure, String componentKey) { + if (measure.getValue() == null && measure.getData() == null) { + throw new IllegalArgumentException(String.format("Measure on metric '%s' and component '%s' has no value, but it's not allowed", measure.getMetricKey(), componentKey)); + } + } + + private ScannerReport.Measure toReportMeasure(ScannerReport.Measure.Builder builder, Measure measure) { + builder.clear(); + + builder.setValueType(getMeasureValueType(measure.getMetric().getType())); + setValueAccordingToType(builder, measure); + // Because some numeric measures also have a data (like maintainability rating) + String data = measure.getData(); + if (data != null) { + builder.setStringValue(data); + } + builder.setMetricKey(measure.getMetricKey()); + return builder.build(); + } + + private void setValueAccordingToType(ScannerReport.Measure.Builder builder, Measure measure) { + Serializable value = measure.value(); + switch (builder.getValueType()) { + case BOOLEAN: + builder.setBooleanValue((Boolean) value); + break; + case DOUBLE: + builder.setDoubleValue(((Number) value).doubleValue()); + break; + case INT: + builder.setIntValue(((Number) value).intValue()); + break; + case LONG: + builder.setLongValue(((Number) value).longValue()); + break; + case STRING: + builder.setStringValue((String) value); + break; + default: + throw new IllegalStateException("Unknown value type: " + builder.getValueType()); + } + } + + private MeasureValueType getMeasureValueType(ValueType type) { + switch (type) { + case INT: + case RATING: + return MeasureValueType.INT; + case FLOAT: + case PERCENT: + return MeasureValueType.DOUBLE; + case BOOL: + return MeasureValueType.BOOLEAN; + case STRING: + case DATA: + case LEVEL: + case DISTRIB: + return MeasureValueType.STRING; + case WORK_DUR: + case MILLISEC: + return MeasureValueType.LONG; + default: + throw new IllegalStateException("Unknown value type: " + type); + } + } + + } + + private static final class IsMetricAllowed implements Predicate<Measure> { + private final Set<String> allowedMetricKeys; + + private IsMetricAllowed(Set<String> allowedMetricKeys) { + this.allowedMetricKeys = allowedMetricKeys; + } + + @Override + public boolean apply(Measure input) { + return allowedMetricKeys.contains(input.getMetricKey()); + } + } + + private static final class MetricToKey implements Function<Metric, String> { + @Override + public String apply(Metric input) { + return input.key(); + } + } + + private final BatchComponentCache resourceCache; + private final MeasureCache measureCache; + private final BatchMetrics batchMetrics; + + public MeasuresPublisher(BatchComponentCache resourceCache, MeasureCache measureCache, BatchMetrics batchMetrics) { + this.resourceCache = resourceCache; + this.measureCache = measureCache; + this.batchMetrics = batchMetrics; + } + + @Override + public void publish(ScannerReportWriter writer) { + final Set<String> allowedMetricKeys = newHashSet(transform(batchMetrics.getMetrics(), new MetricToKey())); + for (final BatchComponent resource : resourceCache.all()) { + Iterable<Measure> batchMeasures = measureCache.byResource(resource.resource()); + Iterable<org.sonar.scanner.protocol.output.ScannerReport.Measure> reportMeasures = transform( + filter(batchMeasures, new IsMetricAllowed(allowedMetricKeys)), + new MeasureToReportMeasure(resource)); + writer.writeComponentMeasures(resource.batchId(), reportMeasures); + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/report/MetadataPublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/report/MetadataPublisher.java new file mode 100644 index 00000000000..67dc6c0be04 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/report/MetadataPublisher.java @@ -0,0 +1,61 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.report; + +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.config.Settings; +import org.sonar.api.resources.Project; +import org.sonar.batch.cpd.index.SonarCpdBlockIndex; +import org.sonar.batch.index.BatchComponent; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.scan.ImmutableProjectReactor; +import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.scanner.protocol.output.ScannerReportWriter; + +public class MetadataPublisher implements ReportPublisherStep { + + private final BatchComponentCache componentCache; + private final ImmutableProjectReactor reactor; + private final Settings settings; + + public MetadataPublisher(BatchComponentCache componentCache, ImmutableProjectReactor reactor, Settings settings) { + this.componentCache = componentCache; + this.reactor = reactor; + this.settings = settings; + } + + @Override + public void publish(ScannerReportWriter writer) { + ProjectDefinition root = reactor.getRoot(); + BatchComponent rootProject = componentCache.getRoot(); + ScannerReport.Metadata.Builder builder = ScannerReport.Metadata.newBuilder() + .setAnalysisDate(((Project) rootProject.resource()).getAnalysisDate().getTime()) + // Here we want key without branch + .setProjectKey(root.getKey()) + .setCrossProjectDuplicationActivated(SonarCpdBlockIndex.isCrossProjectDuplicationEnabled(settings)) + .setRootComponentRef(rootProject.batchId()); + String branch = root.properties().get(CoreProperties.PROJECT_BRANCH_PROPERTY); + if (branch != null) { + builder.setBranch(branch); + } + writer.writeMetadata(builder.build()); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/report/ReportPublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/report/ReportPublisher.java new file mode 100644 index 00000000000..c0487441c10 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/report/ReportPublisher.java @@ -0,0 +1,242 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.report; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Throwables; +import com.google.common.io.Files; +import com.squareup.okhttp.HttpUrl; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Writer; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.LinkedHashMap; +import java.util.Map; +import javax.annotation.Nullable; +import org.apache.commons.io.FileUtils; +import org.picocontainer.Startable; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.config.Settings; +import org.sonar.api.utils.MessageException; +import org.sonar.api.utils.TempFolder; +import org.sonar.api.utils.ZipUtils; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.batch.analysis.DefaultAnalysisMode; +import org.sonar.batch.bootstrap.BatchWsClient; +import org.sonar.batch.scan.ImmutableProjectReactor; +import org.sonar.scanner.protocol.output.ScannerReportWriter; +import org.sonarqube.ws.MediaTypes; +import org.sonarqube.ws.WsCe; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.WsResponse; + +import static org.apache.commons.lang.StringUtils.trimToEmpty; +import static org.sonar.core.util.FileUtils.deleteQuietly; + +@BatchSide +public class ReportPublisher implements Startable { + + private static final Logger LOG = Loggers.get(ReportPublisher.class); + + public static final String KEEP_REPORT_PROP_KEY = "sonar.batch.keepReport"; + public static final String VERBOSE_KEY = "sonar.verbose"; + public static final String METADATA_DUMP_FILENAME = "report-task.txt"; + + private final Settings settings; + private final BatchWsClient wsClient; + private final AnalysisContextReportPublisher contextPublisher; + private final ImmutableProjectReactor projectReactor; + private final DefaultAnalysisMode analysisMode; + private final TempFolder temp; + private final ReportPublisherStep[] publishers; + + private File reportDir; + private ScannerReportWriter writer; + + public ReportPublisher(Settings settings, BatchWsClient wsClient, AnalysisContextReportPublisher contextPublisher, + ImmutableProjectReactor projectReactor, DefaultAnalysisMode analysisMode, TempFolder temp, ReportPublisherStep[] publishers) { + this.settings = settings; + this.wsClient = wsClient; + this.contextPublisher = contextPublisher; + this.projectReactor = projectReactor; + this.analysisMode = analysisMode; + this.temp = temp; + this.publishers = publishers; + } + + @Override + public void start() { + reportDir = new File(projectReactor.getRoot().getWorkDir(), "batch-report"); + writer = new ScannerReportWriter(reportDir); + contextPublisher.init(writer); + + if (!analysisMode.isIssues() && !analysisMode.isMediumTest()) { + String publicUrl = publicUrl(); + if (HttpUrl.parse(publicUrl) == null) { + throw MessageException.of("Failed to parse public URL set in SonarQube server: " + publicUrl); + } + } + } + + @Override + public void stop() { + if (!settings.getBoolean(KEEP_REPORT_PROP_KEY) && !settings.getBoolean(VERBOSE_KEY)) { + deleteQuietly(reportDir); + } else { + LOG.info("Analysis report generated in " + reportDir); + } + } + + public File getReportDir() { + return reportDir; + } + + public ScannerReportWriter getWriter() { + return writer; + } + + public void execute() { + // If this is a issues mode analysis then we should not upload reports + String taskId = null; + if (!analysisMode.isIssues()) { + File report = generateReportFile(); + if (!analysisMode.isMediumTest()) { + taskId = upload(report); + } + } + logSuccess(taskId); + } + + private File generateReportFile() { + try { + long startTime = System.currentTimeMillis(); + for (ReportPublisherStep publisher : publishers) { + publisher.publish(writer); + } + long stopTime = System.currentTimeMillis(); + LOG.info("Analysis report generated in {}ms, dir size={}", stopTime - startTime, FileUtils.byteCountToDisplaySize(FileUtils.sizeOfDirectory(reportDir))); + + startTime = System.currentTimeMillis(); + File reportZip = temp.newFile("batch-report", ".zip"); + ZipUtils.zipDir(reportDir, reportZip); + stopTime = System.currentTimeMillis(); + LOG.info("Analysis reports compressed in {}ms, zip size={}", stopTime - startTime, FileUtils.byteCountToDisplaySize(FileUtils.sizeOf(reportZip))); + return reportZip; + } catch (IOException e) { + throw new IllegalStateException("Unable to prepare analysis report", e); + } + } + + /** + * Uploads the report file to server and returns the generated task id + */ + @VisibleForTesting + String upload(File report) { + LOG.debug("Upload report"); + long startTime = System.currentTimeMillis(); + ProjectDefinition projectDefinition = projectReactor.getRoot(); + PostRequest.Part filePart = new PostRequest.Part(MediaTypes.ZIP, report); + PostRequest post = new PostRequest("api/ce/submit") + .setMediaType(MediaTypes.PROTOBUF) + .setParam("projectKey", projectDefinition.getKey()) + .setParam("projectName", projectDefinition.getName()) + .setParam("projectBranch", projectDefinition.getBranch()) + .setPart("report", filePart); + WsResponse response = wsClient.call(post).failIfNotSuccessful(); + try (InputStream protobuf = response.contentStream()) { + return WsCe.SubmitResponse.parser().parseFrom(protobuf).getTaskId(); + } catch (Exception e) { + throw Throwables.propagate(e); + } finally { + long stopTime = System.currentTimeMillis(); + LOG.info("Analysis report uploaded in " + (stopTime - startTime) + "ms"); + } + } + + @VisibleForTesting + void logSuccess(@Nullable String taskId) { + if (taskId == null) { + LOG.info("ANALYSIS SUCCESSFUL"); + } else { + String publicUrl = publicUrl(); + HttpUrl httpUrl = HttpUrl.parse(publicUrl); + + Map<String, String> metadata = new LinkedHashMap<>(); + String effectiveKey = projectReactor.getRoot().getKeyWithBranch(); + metadata.put("projectKey", effectiveKey); + metadata.put("serverUrl", publicUrl); + + URL dashboardUrl = httpUrl.newBuilder() + .addPathSegment("dashboard").addPathSegment("index").addPathSegment(effectiveKey) + .build() + .url(); + metadata.put("dashboardUrl", dashboardUrl.toExternalForm()); + + URL taskUrl = HttpUrl.parse(publicUrl).newBuilder() + .addPathSegment("api").addPathSegment("ce").addPathSegment("task") + .addQueryParameter("id", taskId) + .build() + .url(); + metadata.put("ceTaskId", taskId); + metadata.put("ceTaskUrl", taskUrl.toExternalForm()); + + LOG.info("ANALYSIS SUCCESSFUL, you can browse {}", dashboardUrl); + LOG.info("Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report"); + LOG.info("More about the report processing at {}", taskUrl); + + dumpMetadata(metadata); + } + } + + private void dumpMetadata(Map<String, String> metadata) { + File file = new File(projectReactor.getRoot().getWorkDir(), METADATA_DUMP_FILENAME); + try (Writer output = Files.newWriter(file, StandardCharsets.UTF_8)) { + for (Map.Entry<String, String> entry : metadata.entrySet()) { + output.write(entry.getKey()); + output.write("="); + output.write(entry.getValue()); + output.write("\n"); + } + + LOG.debug("Report metadata written to {}", file); + } catch (IOException e) { + throw new IllegalStateException("Unable to dump " + file, e); + } + } + + /** + * The public URL is optionally configured on server. If not, then the regular URL is returned. + * See https://jira.sonarsource.com/browse/SONAR-4239 + */ + private String publicUrl() { + String baseUrl = trimToEmpty(settings.getString(CoreProperties.SERVER_BASE_URL)); + if (baseUrl.equals(settings.getDefaultValue(CoreProperties.SERVER_BASE_URL))) { + // crap workaround for https://jira.sonarsource.com/browse/SONAR-7109 + // If server base URL was not configured in Sonar server then is is better to take URL configured on batch side + baseUrl = wsClient.baseUrl(); + } + return baseUrl.replaceAll("(/)+$", ""); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/report/ReportPublisherStep.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/report/ReportPublisherStep.java new file mode 100644 index 00000000000..8edcacb4b48 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/report/ReportPublisherStep.java @@ -0,0 +1,31 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.report; + +import org.sonar.scanner.protocol.output.ScannerReportWriter; + +/** + * Adds a sub-part of data to output report + */ +public interface ReportPublisherStep { + + void publish(ScannerReportWriter writer); + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/report/ScannerReportUtils.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/report/ScannerReportUtils.java new file mode 100644 index 00000000000..b5e026476d3 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/report/ScannerReportUtils.java @@ -0,0 +1,83 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.report; + +import org.sonar.api.batch.sensor.highlighting.TypeOfText; +import org.sonar.scanner.protocol.Constants.HighlightingType; + +public class ScannerReportUtils { + + private ScannerReportUtils() { + } + + public static HighlightingType toProtocolType(TypeOfText textType) { + switch (textType) { + case ANNOTATION: + return HighlightingType.ANNOTATION; + case COMMENT: + return HighlightingType.COMMENT; + case CONSTANT: + return HighlightingType.CONSTANT; + case CPP_DOC: + return HighlightingType.CPP_DOC; + case KEYWORD: + return HighlightingType.KEYWORD; + case KEYWORD_LIGHT: + return HighlightingType.KEYWORD_LIGHT; + case PREPROCESS_DIRECTIVE: + return HighlightingType.PREPROCESS_DIRECTIVE; + case STRING: + return HighlightingType.HIGHLIGHTING_STRING; + case STRUCTURED_COMMENT: + return HighlightingType.STRUCTURED_COMMENT; + default: + throw new IllegalArgumentException("Unknow highlighting type: " + textType); + } + } + + public static TypeOfText toBatchType(HighlightingType type) { + switch (type) { + case ANNOTATION: + return TypeOfText.ANNOTATION; + case COMMENT: + return TypeOfText.COMMENT; + case CONSTANT: + return TypeOfText.CONSTANT; + case CPP_DOC: + return TypeOfText.CPP_DOC; + case HIGHLIGHTING_STRING: + return TypeOfText.STRING; + case KEYWORD: + return TypeOfText.KEYWORD; + case KEYWORD_LIGHT: + return TypeOfText.KEYWORD_LIGHT; + case PREPROCESS_DIRECTIVE: + return TypeOfText.PREPROCESS_DIRECTIVE; + case STRUCTURED_COMMENT: + return TypeOfText.STRUCTURED_COMMENT; + default: + throw new IllegalArgumentException(type + " is not a valid type"); + } + } + + public static String toCssClass(HighlightingType type) { + return toBatchType(type).cssClass(); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/report/SourcePublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/report/SourcePublisher.java new file mode 100644 index 00000000000..b6b5a1d1745 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/report/SourcePublisher.java @@ -0,0 +1,72 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.report; + +import org.apache.commons.io.ByteOrderMark; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.BOMInputStream; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.batch.index.BatchComponent; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.scanner.protocol.output.ScannerReportWriter; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +public class SourcePublisher implements ReportPublisherStep { + + private final BatchComponentCache resourceCache; + + public SourcePublisher(BatchComponentCache resourceCache) { + this.resourceCache = resourceCache; + } + + @Override + public void publish(ScannerReportWriter writer) { + for (final BatchComponent resource : resourceCache.all()) { + if (!resource.isFile()) { + continue; + } + + DefaultInputFile inputFile = (DefaultInputFile) resource.inputComponent(); + File iofile = writer.getSourceFile(resource.batchId()); + int line = 0; + try (FileOutputStream output = new FileOutputStream(iofile); BOMInputStream bomIn = new BOMInputStream(new FileInputStream(inputFile.file()), + ByteOrderMark.UTF_8, ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_16BE, ByteOrderMark.UTF_32LE, ByteOrderMark.UTF_32BE); + BufferedReader reader = new BufferedReader(new InputStreamReader(bomIn, inputFile.charset()))) { + String lineStr = reader.readLine(); + while (lineStr != null) { + IOUtils.write(lineStr, output, StandardCharsets.UTF_8); + line++; + if (line < inputFile.lines()) { + IOUtils.write("\n", output, StandardCharsets.UTF_8); + } + lineStr = reader.readLine(); + } + } catch (IOException e) { + throw new IllegalStateException("Unable to store file source in the report", e); + } + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/report/TestExecutionAndCoveragePublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/report/TestExecutionAndCoveragePublisher.java new file mode 100644 index 00000000000..7561eb1ed1d --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/report/TestExecutionAndCoveragePublisher.java @@ -0,0 +1,139 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.report; + +import com.google.common.base.Function; +import com.google.common.collect.Iterables; +import java.util.HashSet; +import java.util.Set; +import javax.annotation.Nonnull; +import org.sonar.api.batch.fs.InputFile.Type; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.test.CoverageBlock; +import org.sonar.api.test.MutableTestCase; +import org.sonar.api.test.MutableTestPlan; +import org.sonar.api.test.TestCase; +import org.sonar.batch.index.BatchComponent; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.test.DefaultTestable; +import org.sonar.batch.test.TestPlanBuilder; +import org.sonar.scanner.protocol.Constants.TestStatus; +import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.scanner.protocol.output.ScannerReportWriter; +import org.sonar.scanner.protocol.output.ScannerReport.CoverageDetail; +import org.sonar.scanner.protocol.output.ScannerReport.Test; + +public class TestExecutionAndCoveragePublisher implements ReportPublisherStep { + + private static final class TestConverter implements Function<MutableTestCase, ScannerReport.Test> { + private final Set<String> testNamesWithCoverage; + private ScannerReport.Test.Builder builder = ScannerReport.Test.newBuilder(); + + private TestConverter(Set<String> testNamesWithCoverage) { + this.testNamesWithCoverage = testNamesWithCoverage; + } + + @Override + public Test apply(@Nonnull MutableTestCase testCase) { + builder.clear(); + builder.setName(testCase.name()); + if (testCase.doesCover()) { + testNamesWithCoverage.add(testCase.name()); + } + Long durationInMs = testCase.durationInMs(); + if (durationInMs != null) { + builder.setDurationInMs(durationInMs); + } + String msg = testCase.message(); + if (msg != null) { + builder.setMsg(msg); + } + String stack = testCase.stackTrace(); + if (stack != null) { + builder.setStacktrace(stack); + } + TestCase.Status status = testCase.status(); + if (status != null) { + builder.setStatus(TestStatus.valueOf(status.name())); + } + return builder.build(); + } + } + + private final class TestCoverageConverter implements Function<String, CoverageDetail> { + private final MutableTestPlan testPlan; + private ScannerReport.CoverageDetail.Builder builder = ScannerReport.CoverageDetail.newBuilder(); + private ScannerReport.CoverageDetail.CoveredFile.Builder coveredBuilder = ScannerReport.CoverageDetail.CoveredFile.newBuilder(); + + private TestCoverageConverter(MutableTestPlan testPlan) { + this.testPlan = testPlan; + } + + @Override + public CoverageDetail apply(@Nonnull String testName) { + // Take first test with provided name + MutableTestCase testCase = testPlan.testCasesByName(testName).iterator().next(); + builder.clear(); + builder.setTestName(testName); + for (CoverageBlock block : testCase.coverageBlocks()) { + coveredBuilder.clear(); + coveredBuilder.setFileRef(componentCache.get(((DefaultTestable) block.testable()).inputFile().key()).batchId()); + for (int line : block.lines()) { + coveredBuilder.addCoveredLine(line); + } + builder.addCoveredFile(coveredBuilder.build()); + } + return builder.build(); + } + } + + private final BatchComponentCache componentCache; + private final TestPlanBuilder testPlanBuilder; + + public TestExecutionAndCoveragePublisher(BatchComponentCache resourceCache, TestPlanBuilder testPlanBuilder) { + this.componentCache = resourceCache; + this.testPlanBuilder = testPlanBuilder; + } + + @Override + public void publish(ScannerReportWriter writer) { + for (final BatchComponent component : componentCache.all()) { + if (!component.isFile()) { + continue; + } + + DefaultInputFile inputFile = (DefaultInputFile) component.inputComponent(); + if (inputFile.type() != Type.TEST) { + continue; + } + + final MutableTestPlan testPlan = testPlanBuilder.loadPerspective(MutableTestPlan.class, component); + if (testPlan == null || Iterables.isEmpty(testPlan.testCases())) { + continue; + } + + final Set<String> testNamesWithCoverage = new HashSet<>(); + + writer.writeTests(component.batchId(), Iterables.transform(testPlan.testCases(), new TestConverter(testNamesWithCoverage))); + + writer.writeCoverageDetails(component.batchId(), Iterables.transform(testNamesWithCoverage, new TestCoverageConverter(testPlan))); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/report/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/report/package-info.java new file mode 100644 index 00000000000..f4b3d8e51a9 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/report/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.report; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/DefaultGlobalRepositoriesLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/DefaultGlobalRepositoriesLoader.java new file mode 100644 index 00000000000..55508b0e8bf --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/DefaultGlobalRepositoriesLoader.java @@ -0,0 +1,48 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.repository; + +import org.sonar.batch.cache.WSLoaderResult; +import org.sonar.scanner.protocol.input.GlobalRepositories; +import org.sonar.batch.cache.WSLoader; + +import javax.annotation.Nullable; + +import org.apache.commons.lang.mutable.MutableBoolean; + +public class DefaultGlobalRepositoriesLoader implements GlobalRepositoriesLoader { + + private static final String BATCH_GLOBAL_URL = "/batch/global"; + + private final WSLoader wsLoader; + + public DefaultGlobalRepositoriesLoader(WSLoader wsLoader) { + this.wsLoader = wsLoader; + } + + @Override + public GlobalRepositories load(@Nullable MutableBoolean fromCache) { + WSLoaderResult<String> result = wsLoader.loadString(BATCH_GLOBAL_URL); + if (fromCache != null) { + fromCache.setValue(result.isFromCache()); + } + return GlobalRepositories.fromJson(result.get()); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoader.java new file mode 100644 index 00000000000..0e8a0904b57 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoader.java @@ -0,0 +1,129 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.repository; + +import com.google.common.base.Throwables; + +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.util.Date; +import java.util.Map; + +import javax.annotation.Nullable; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.mutable.MutableBoolean; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.utils.MessageException; +import org.sonar.batch.cache.WSLoader; +import org.sonar.batch.cache.WSLoaderResult; +import org.sonar.batch.util.BatchUtils; +import org.sonarqube.ws.WsBatch.WsProjectResponse; +import org.sonarqube.ws.WsBatch.WsProjectResponse.FileDataByPath; +import org.sonarqube.ws.WsBatch.WsProjectResponse.Settings; +import org.sonarqube.ws.client.HttpException; + +public class DefaultProjectRepositoriesLoader implements ProjectRepositoriesLoader { + private static final Logger LOG = LoggerFactory.getLogger(DefaultProjectRepositoriesLoader.class); + private static final String BATCH_PROJECT_URL = "/batch/project.protobuf"; + private final WSLoader loader; + + public DefaultProjectRepositoriesLoader(WSLoader loader) { + this.loader = loader; + } + + @Override + public ProjectRepositories load(String projectKey, boolean issuesMode, @Nullable MutableBoolean fromCache) { + try { + WSLoaderResult<InputStream> result = loader.loadStream(getUrl(projectKey, issuesMode)); + if (fromCache != null) { + fromCache.setValue(result.isFromCache()); + } + return processStream(result.get(), projectKey); + } catch (RuntimeException e) { + if (shouldThrow(e)) { + throw e; + } + + LOG.debug("Project repository not available - continuing without it", e); + return new ProjectRepositories(); + } + } + + private static String getUrl(String projectKey, boolean issuesMode) { + StringBuilder builder = new StringBuilder(); + + builder.append(BATCH_PROJECT_URL) + .append("?key=").append(BatchUtils.encodeForUrl(projectKey)); + if (issuesMode) { + builder.append("&issues_mode=true"); + } + return builder.toString(); + } + + private static boolean shouldThrow(Exception e) { + for (Throwable t : Throwables.getCausalChain(e)) { + if (t instanceof HttpException) { + HttpException http = (HttpException) t; + return http.code() != HttpURLConnection.HTTP_NOT_FOUND; + } + if (t instanceof MessageException) { + return true; + } + } + + return false; + } + + private static ProjectRepositories processStream(InputStream is, String projectKey) { + try { + WsProjectResponse response = WsProjectResponse.parseFrom(is); + + Table<String, String, FileData> fileDataTable = HashBasedTable.create(); + Table<String, String, String> settings = HashBasedTable.create(); + + Map<String, Settings> settingsByModule = response.getSettingsByModule(); + for (Map.Entry<String, Settings> e1 : settingsByModule.entrySet()) { + for (Map.Entry<String, String> e2 : e1.getValue().getSettings().entrySet()) { + settings.put(e1.getKey(), e2.getKey(), e2.getValue()); + } + } + + Map<String, FileDataByPath> fileDataByModuleAndPath = response.getFileDataByModuleAndPath(); + for (Map.Entry<String, FileDataByPath> e1 : fileDataByModuleAndPath.entrySet()) { + for (Map.Entry<String, org.sonarqube.ws.WsBatch.WsProjectResponse.FileData> e2 : e1.getValue().getFileDataByPath().entrySet()) { + FileData fd = new FileData(e2.getValue().getHash(), e2.getValue().getRevision()); + fileDataTable.put(e1.getKey(), e2.getKey(), fd); + } + } + + return new ProjectRepositories(settings, fileDataTable, new Date(response.getLastAnalysisDate())); + } catch (IOException e) { + throw new IllegalStateException("Couldn't load project repository for " + projectKey, e); + } finally { + IOUtils.closeQuietly(is); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/DefaultQualityProfileLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/DefaultQualityProfileLoader.java new file mode 100644 index 00000000000..524e6f6856d --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/DefaultQualityProfileLoader.java @@ -0,0 +1,88 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.repository; + +import org.sonar.api.utils.MessageException; + +import org.sonarqube.ws.QualityProfiles.SearchWsResponse; +import org.sonar.batch.util.BatchUtils; +import org.apache.commons.io.IOUtils; +import org.sonarqube.ws.QualityProfiles.SearchWsResponse.QualityProfile; +import org.apache.commons.lang.mutable.MutableBoolean; +import org.sonar.batch.cache.WSLoaderResult; +import org.sonar.batch.cache.WSLoader; + +import javax.annotation.Nullable; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +public class DefaultQualityProfileLoader implements QualityProfileLoader { + private static final String WS_URL = "/api/qualityprofiles/search.protobuf"; + + private WSLoader wsLoader; + + public DefaultQualityProfileLoader(WSLoader wsLoader) { + this.wsLoader = wsLoader; + } + + @Override + public List<QualityProfile> loadDefault(@Nullable String profileName, @Nullable MutableBoolean fromCache) { + String url = WS_URL + "?defaults=true"; + if (profileName != null) { + url += "&profileName=" + BatchUtils.encodeForUrl(profileName); + } + return loadResource(url, fromCache); + } + + @Override + public List<QualityProfile> load(String projectKey, @Nullable String profileName, @Nullable MutableBoolean fromCache) { + String url = WS_URL + "?projectKey=" + BatchUtils.encodeForUrl(projectKey); + if (profileName != null) { + url += "&profileName=" + BatchUtils.encodeForUrl(profileName); + } + return loadResource(url, fromCache); + } + + private List<QualityProfile> loadResource(String url, @Nullable MutableBoolean fromCache) { + WSLoaderResult<InputStream> result = wsLoader.loadStream(url); + if (fromCache != null) { + fromCache.setValue(result.isFromCache()); + } + InputStream is = result.get(); + SearchWsResponse profiles = null; + + try { + profiles = SearchWsResponse.parseFrom(is); + } catch (IOException e) { + throw new IllegalStateException("Failed to load quality profiles", e); + } finally { + IOUtils.closeQuietly(is); + } + + List<QualityProfile> profilesList = profiles.getProfilesList(); + if (profilesList == null || profilesList.isEmpty()) { + throw MessageException.of("No quality profiles have been found, you probably don't have any language plugin installed."); + } + return profilesList; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/DefaultServerIssuesLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/DefaultServerIssuesLoader.java new file mode 100644 index 00000000000..394ce0eea92 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/DefaultServerIssuesLoader.java @@ -0,0 +1,59 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.repository; + +import com.google.common.base.Function; +import java.io.IOException; +import java.io.InputStream; +import org.apache.commons.io.IOUtils; +import org.sonar.batch.cache.WSLoader; +import org.sonar.batch.cache.WSLoaderResult; +import org.sonar.batch.util.BatchUtils; +import org.sonar.scanner.protocol.input.ScannerInput.ServerIssue; + +public class DefaultServerIssuesLoader implements ServerIssuesLoader { + + private final WSLoader wsLoader; + + public DefaultServerIssuesLoader(WSLoader wsLoader) { + this.wsLoader = wsLoader; + } + + @Override + public boolean load(String componentKey, Function<ServerIssue, Void> consumer) { + WSLoaderResult<InputStream> result = wsLoader.loadStream("/batch/issues.protobuf?key=" + BatchUtils.encodeForUrl(componentKey)); + parseIssues(result.get(), consumer); + return result.isFromCache(); + } + + private static void parseIssues(InputStream is, Function<ServerIssue, Void> consumer) { + try { + ServerIssue previousIssue = ServerIssue.parseDelimitedFrom(is); + while (previousIssue != null) { + consumer.apply(previousIssue); + previousIssue = ServerIssue.parseDelimitedFrom(is); + } + } catch (IOException e) { + throw new IllegalStateException("Unable to get previous issues", e); + } finally { + IOUtils.closeQuietly(is); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/FileData.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/FileData.java new file mode 100644 index 00000000000..bcf470e8986 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/FileData.java @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.repository; + +import javax.annotation.concurrent.Immutable; + +@Immutable +public class FileData { + private final String hash; + private final String revision; + + public FileData(String hash, String revision) { + this.hash = hash; + this.revision = revision; + } + + public String hash() { + return hash; + } + + public String revision() { + return revision; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/GlobalRepositoriesLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/GlobalRepositoriesLoader.java new file mode 100644 index 00000000000..86359e4b772 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/GlobalRepositoriesLoader.java @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.repository; + +import org.apache.commons.lang.mutable.MutableBoolean; +import org.sonar.scanner.protocol.input.GlobalRepositories; +import javax.annotation.Nullable; + +public interface GlobalRepositoriesLoader { + + GlobalRepositories load(@Nullable MutableBoolean fromCache); + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/GlobalRepositoriesProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/GlobalRepositoriesProvider.java new file mode 100644 index 00000000000..578b5458cd8 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/GlobalRepositoriesProvider.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.repository; + +import org.apache.commons.lang.mutable.MutableBoolean; + +import org.picocontainer.injectors.ProviderAdapter; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.api.utils.log.Profiler; +import org.sonar.scanner.protocol.input.GlobalRepositories; + +public class GlobalRepositoriesProvider extends ProviderAdapter { + + private static final Logger LOG = Loggers.get(GlobalRepositoriesProvider.class); + private static final String LOG_MSG = "Load global repositories"; + private GlobalRepositories globalReferentials; + + public GlobalRepositories provide(GlobalRepositoriesLoader loader) { + if (globalReferentials == null) { + Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG); + MutableBoolean fromCache = new MutableBoolean(); + globalReferentials = loader.load(fromCache); + profiler.stopInfo(fromCache.booleanValue()); + } + return globalReferentials; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/ProjectRepositories.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/ProjectRepositories.java new file mode 100644 index 00000000000..baba0c52396 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/ProjectRepositories.java @@ -0,0 +1,79 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.repository; + +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; +import java.util.Date; +import java.util.Map; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + +public class ProjectRepositories { + private final Table<String, String, String> settingsByModule; + private final Table<String, String, FileData> fileDataByModuleAndPath; + private final Date lastAnalysisDate; + private final boolean exists; + + public ProjectRepositories() { + this.exists = false; + this.settingsByModule = HashBasedTable.create(); + this.fileDataByModuleAndPath = HashBasedTable.create(); + this.lastAnalysisDate = null; + } + + public ProjectRepositories(Table<String, String, String> settingsByModule, Table<String, String, FileData> fileDataByModuleAndPath, + @Nullable Date lastAnalysisDate) { + this.settingsByModule = settingsByModule; + this.fileDataByModuleAndPath = fileDataByModuleAndPath; + this.lastAnalysisDate = lastAnalysisDate; + this.exists = true; + } + + public boolean exists() { + return exists; + } + + public Map<String, FileData> fileDataByPath(String moduleKey) { + return fileDataByModuleAndPath.row(moduleKey); + } + + public Table<String, String, FileData> fileDataByModuleAndPath() { + return fileDataByModuleAndPath; + } + + public boolean moduleExists(String moduleKey) { + return settingsByModule.containsRow(moduleKey); + } + + public Map<String, String> settings(String moduleKey) { + return settingsByModule.row(moduleKey); + } + + @CheckForNull + public FileData fileData(String projectKey, String path) { + return fileDataByModuleAndPath.get(projectKey, path); + } + + @CheckForNull + public Date lastAnalysisDate() { + return lastAnalysisDate; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/ProjectRepositoriesLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/ProjectRepositoriesLoader.java new file mode 100644 index 00000000000..53de4805324 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/ProjectRepositoriesLoader.java @@ -0,0 +1,28 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.repository; + +import javax.annotation.Nullable; + +import org.apache.commons.lang.mutable.MutableBoolean; + +public interface ProjectRepositoriesLoader { + ProjectRepositories load(String projectKeyWithBranch, boolean issuesMode, @Nullable MutableBoolean fromCache); +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/ProjectRepositoriesProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/ProjectRepositoriesProvider.java new file mode 100644 index 00000000000..1e0469651d5 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/ProjectRepositoriesProvider.java @@ -0,0 +1,68 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.repository; + +import org.sonar.api.utils.log.Profiler; + +import org.sonar.api.batch.bootstrap.ProjectKey; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; + +import org.sonar.batch.analysis.DefaultAnalysisMode; +import org.apache.commons.lang.mutable.MutableBoolean; +import org.picocontainer.injectors.ProviderAdapter; + +public class ProjectRepositoriesProvider extends ProviderAdapter { + private static final Logger LOG = Loggers.get(ProjectRepositoriesProvider.class); + private static final String LOG_MSG = "Load project repositories"; + private ProjectRepositories project = null; + + public ProjectRepositories provide(ProjectRepositoriesLoader loader, ProjectKey projectKey, DefaultAnalysisMode mode) { + if (project == null) { + MutableBoolean fromCache = new MutableBoolean(false); + Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG); + if (mode.isNotAssociated()) { + project = createNonAssociatedProjectRepositories(); + profiler.stopInfo(); + } else { + project = loader.load(projectKey.get(), mode.isIssues(), fromCache); + checkProject(mode); + profiler.stopInfo(fromCache.booleanValue()); + } + + } + + return project; + } + + private void checkProject(DefaultAnalysisMode mode) { + if (mode.isIssues()) { + if (!project.exists()) { + LOG.warn("Project doesn't exist on the server. All issues will be marked as 'new'."); + } else if (project.lastAnalysisDate() == null && !mode.isNotAssociated()) { + LOG.warn("No analysis has been found on the server for this project. All issues will be marked as 'new'."); + } + } + } + + private static ProjectRepositories createNonAssociatedProjectRepositories() { + return new ProjectRepositories(); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/QualityProfileLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/QualityProfileLoader.java new file mode 100644 index 00000000000..a93bea66b11 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/QualityProfileLoader.java @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.repository; + +import org.apache.commons.lang.mutable.MutableBoolean; +import org.sonarqube.ws.QualityProfiles.SearchWsResponse.QualityProfile; + +import javax.annotation.Nullable; + +import java.util.List; + +public interface QualityProfileLoader { + List<QualityProfile> load(String projectKey, @Nullable String profileName, @Nullable MutableBoolean fromCache); + + List<QualityProfile> loadDefault(@Nullable String profileName, @Nullable MutableBoolean fromCache); +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/QualityProfileProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/QualityProfileProvider.java new file mode 100644 index 00000000000..6878d21ecfb --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/QualityProfileProvider.java @@ -0,0 +1,69 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.repository; + +import java.util.List; +import javax.annotation.CheckForNull; +import org.apache.commons.lang.mutable.MutableBoolean; +import org.picocontainer.injectors.ProviderAdapter; +import org.sonar.api.batch.bootstrap.ProjectKey; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.api.utils.log.Profiler; +import org.sonar.batch.analysis.AnalysisProperties; +import org.sonar.batch.analysis.DefaultAnalysisMode; +import org.sonar.batch.rule.ModuleQProfiles; +import org.sonarqube.ws.QualityProfiles.SearchWsResponse.QualityProfile; + +public class QualityProfileProvider extends ProviderAdapter { + private static final Logger LOG = Loggers.get(QualityProfileProvider.class); + private static final String LOG_MSG = "Load quality profiles"; + private ModuleQProfiles profiles = null; + + public ModuleQProfiles provide(ProjectKey projectKey, QualityProfileLoader loader, ProjectRepositories projectRepositories, AnalysisProperties props, DefaultAnalysisMode mode) { + if (this.profiles == null) { + List<QualityProfile> profileList; + MutableBoolean fromCache = new MutableBoolean(); + + Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG); + if (mode.isNotAssociated() || !projectRepositories.exists()) { + profileList = loader.loadDefault(getSonarProfile(props, mode), fromCache); + } else { + profileList = loader.load(projectKey.get(), getSonarProfile(props, mode), fromCache); + } + profiler.stopInfo(fromCache.booleanValue()); + profiles = new ModuleQProfiles(profileList); + } + + return profiles; + } + + @CheckForNull + private static String getSonarProfile(AnalysisProperties props, DefaultAnalysisMode mode) { + String profile = null; + if (!mode.isIssues() && props.properties().containsKey(ModuleQProfiles.SONAR_PROFILE_PROP)) { + profile = props.property(ModuleQProfiles.SONAR_PROFILE_PROP); + LOG.warn("Ability to set quality profile from command line using '" + ModuleQProfiles.SONAR_PROFILE_PROP + + "' is deprecated and will be dropped in a future SonarQube version. Please configure quality profile used by your project on SonarQube server."); + } + return profile; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/ServerIssuesLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/ServerIssuesLoader.java new file mode 100644 index 00000000000..e0c981a11e0 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/ServerIssuesLoader.java @@ -0,0 +1,29 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.repository; + +import com.google.common.base.Function; +import org.sonar.scanner.protocol.input.ScannerInput.ServerIssue; + +public interface ServerIssuesLoader { + + boolean load(String componentKey, Function<ServerIssue, Void> consumer); + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/language/DefaultLanguagesRepository.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/language/DefaultLanguagesRepository.java new file mode 100644 index 00000000000..4f891e64f3d --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/language/DefaultLanguagesRepository.java @@ -0,0 +1,78 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.repository.language; + +import org.picocontainer.Startable; + +import org.sonar.api.resources.Languages; + +import javax.annotation.CheckForNull; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Languages repository using {@link Languages} + * @since 4.4 + */ +public class DefaultLanguagesRepository implements LanguagesRepository, Startable { + + private Languages languages; + + public DefaultLanguagesRepository(Languages languages) { + this.languages = languages; + } + + @Override + public void start() { + if (languages.all().length == 0) { + throw new IllegalStateException("No language plugins are installed."); + } + } + + /** + * Get language. + */ + @Override + @CheckForNull + public Language get(String languageKey) { + org.sonar.api.resources.Language language = languages.get(languageKey); + return language != null ? new Language(language.getKey(), language.getName(), language.getFileSuffixes()) : null; + } + + /** + * Get list of all supported languages. + */ + @Override + public Collection<Language> all() { + org.sonar.api.resources.Language[] all = languages.all(); + Collection<Language> result = new ArrayList<>(all.length); + for (org.sonar.api.resources.Language language : all) { + result.add(new Language(language.getKey(), language.getName(), language.getFileSuffixes())); + } + return result; + } + + @Override + public void stop() { + // nothing to do + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/language/Language.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/language/Language.java new file mode 100644 index 00000000000..1a9ae783536 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/language/Language.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.repository.language; + +import java.util.Arrays; +import java.util.Collection; + +public final class Language { + + private final String key; + private final String name; + private final String[] fileSuffixes; + + public Language(String key, String name, String... fileSuffixes) { + this.key = key; + this.name = name; + this.fileSuffixes = fileSuffixes; + } + + /** + * For example "java". + */ + public String key() { + return key; + } + + /** + * For example "Java" + */ + public String name() { + return name; + } + + /** + * For example ["jav", "java"]. + */ + public Collection<String> fileSuffixes() { + return Arrays.asList(fileSuffixes); + } + + @Override + public String toString() { + return name; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/language/LanguagesRepository.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/language/LanguagesRepository.java new file mode 100644 index 00000000000..da1bab7993f --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/language/LanguagesRepository.java @@ -0,0 +1,46 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.repository.language; + +import org.sonar.api.batch.BatchSide; + +import javax.annotation.CheckForNull; + +import java.util.Collection; + +/** + * Languages repository + * @since 4.4 + */ +@BatchSide +public interface LanguagesRepository { + + /** + * Get language. + */ + @CheckForNull + Language get(String languageKey); + + /** + * Get list of all supported languages. + */ + Collection<Language> all(); + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/language/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/language/package-info.java new file mode 100644 index 00000000000..03b2bf5f676 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/language/package-info.java @@ -0,0 +1,21 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@javax.annotation.ParametersAreNonnullByDefault +package org.sonar.batch.repository.language; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/package-info.java new file mode 100644 index 00000000000..3679d1bfbb1 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.repository; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/user/UserRepositoryLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/user/UserRepositoryLoader.java new file mode 100644 index 00000000000..9b33645cebe --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/user/UserRepositoryLoader.java @@ -0,0 +1,117 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.repository.user; + +import org.apache.commons.io.IOUtils; + +import org.sonar.batch.cache.WSLoaderResult; +import org.sonar.batch.cache.WSLoader; + +import javax.annotation.Nullable; + +import org.apache.commons.lang.mutable.MutableBoolean; +import com.google.common.collect.Lists; +import com.google.common.base.Joiner; +import org.sonar.batch.util.BatchUtils; +import org.sonar.scanner.protocol.input.ScannerInput; +import com.google.common.base.Function; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class UserRepositoryLoader { + private final WSLoader wsLoader; + + public UserRepositoryLoader(WSLoader wsLoader) { + this.wsLoader = wsLoader; + } + + public ScannerInput.User load(String userLogin) { + return load(userLogin, null); + } + + public ScannerInput.User load(String userLogin, @Nullable MutableBoolean fromCache) { + InputStream is = loadQuery(new UserEncodingFunction().apply(userLogin), fromCache); + return parseUser(is); + } + + public Collection<ScannerInput.User> load(List<String> userLogins) { + return load(userLogins, null); + } + + /** + * Not cache friendly. Should not be used if a cache hit is expected. + */ + public Collection<ScannerInput.User> load(List<String> userLogins, @Nullable MutableBoolean fromCache) { + if (userLogins.isEmpty()) { + return Collections.emptyList(); + } + InputStream is = loadQuery(Joiner.on(',').join(Lists.transform(userLogins, new UserEncodingFunction())), fromCache); + + return parseUsers(is); + } + + private InputStream loadQuery(String loginsQuery, @Nullable MutableBoolean fromCache) { + WSLoaderResult<InputStream> result = wsLoader.loadStream("/batch/users?logins=" + loginsQuery); + if (fromCache != null) { + fromCache.setValue(result.isFromCache()); + } + return result.get(); + } + + private static class UserEncodingFunction implements Function<String, String> { + @Override + public String apply(String input) { + return BatchUtils.encodeForUrl(input); + } + } + + private static ScannerInput.User parseUser(InputStream is) { + try { + return ScannerInput.User.parseDelimitedFrom(is); + } catch (IOException e) { + throw new IllegalStateException("Unable to get user details from server", e); + } finally { + IOUtils.closeQuietly(is); + } + } + + private static Collection<ScannerInput.User> parseUsers(InputStream is) { + List<ScannerInput.User> users = new ArrayList<>(); + + try { + ScannerInput.User user = ScannerInput.User.parseDelimitedFrom(is); + while (user != null) { + users.add(user); + user = ScannerInput.User.parseDelimitedFrom(is); + } + } catch (IOException e) { + throw new IllegalStateException("Unable to get user details from server", e); + } finally { + IOUtils.closeQuietly(is); + } + + return users; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/user/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/user/package-info.java new file mode 100644 index 00000000000..6462fb00b8a --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/repository/user/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.repository.user; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/ActiveRulesLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/ActiveRulesLoader.java new file mode 100644 index 00000000000..028929bd141 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/ActiveRulesLoader.java @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.rule; + +import org.apache.commons.lang.mutable.MutableBoolean; + +import javax.annotation.Nullable; + +import java.util.List; + +public interface ActiveRulesLoader { + List<LoadedActiveRule> load(String qualityProfileKey, @Nullable MutableBoolean fromCache); +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/ActiveRulesProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/ActiveRulesProvider.java new file mode 100644 index 00000000000..83034e22132 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/ActiveRulesProvider.java @@ -0,0 +1,116 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.rule; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.commons.lang.mutable.MutableBoolean; +import org.picocontainer.injectors.ProviderAdapter; +import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.batch.rule.internal.ActiveRulesBuilder; +import org.sonar.api.batch.rule.internal.NewActiveRule; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.api.utils.log.Profiler; + +/** + * Loads the rules that are activated on the Quality profiles + * used by the current project and builds {@link org.sonar.api.batch.rule.ActiveRules}. + */ +public class ActiveRulesProvider extends ProviderAdapter { + private static final Logger LOG = Loggers.get(ActiveRulesProvider.class); + private static final String LOG_MSG = "Load active rules"; + private ActiveRules singleton = null; + + public ActiveRules provide(ActiveRulesLoader loader, ModuleQProfiles qProfiles) { + if (singleton == null) { + Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG); + MutableBoolean fromCache = new MutableBoolean(); + singleton = load(loader, qProfiles, fromCache); + profiler.stopInfo(fromCache.booleanValue()); + } + return singleton; + } + + private static ActiveRules load(ActiveRulesLoader loader, ModuleQProfiles qProfiles, MutableBoolean fromCache) { + + Collection<String> qProfileKeys = getKeys(qProfiles); + Map<RuleKey, LoadedActiveRule> loadedRulesByKey = new HashMap<>(); + + try { + for (String qProfileKey : qProfileKeys) { + Collection<LoadedActiveRule> qProfileRules; + qProfileRules = load(loader, qProfileKey, fromCache); + + for (LoadedActiveRule r : qProfileRules) { + if (!loadedRulesByKey.containsKey(r.getRuleKey())) { + loadedRulesByKey.put(r.getRuleKey(), r); + } + } + } + } catch (IOException e) { + throw new IllegalStateException("Error loading active rules", e); + } + + return transform(loadedRulesByKey.values()); + } + + private static ActiveRules transform(Collection<LoadedActiveRule> loadedRules) { + ActiveRulesBuilder builder = new ActiveRulesBuilder(); + + for (LoadedActiveRule activeRule : loadedRules) { + NewActiveRule newActiveRule = builder.create(activeRule.getRuleKey()); + newActiveRule.setName(activeRule.getName()); + newActiveRule.setSeverity(activeRule.getSeverity()); + newActiveRule.setLanguage(activeRule.getLanguage()); + newActiveRule.setInternalKey(activeRule.getInternalKey()); + newActiveRule.setTemplateRuleKey(activeRule.getTemplateRuleKey()); + + // load parameters + if (activeRule.getParams() != null) { + for (Map.Entry<String, String> params : activeRule.getParams().entrySet()) { + newActiveRule.setParam(params.getKey(), params.getValue()); + } + } + + newActiveRule.activate(); + } + return builder.build(); + } + + private static List<LoadedActiveRule> load(ActiveRulesLoader loader, String qProfileKey, MutableBoolean fromCache) throws IOException { + return loader.load(qProfileKey, fromCache); + } + + private static Collection<String> getKeys(ModuleQProfiles qProfiles) { + List<String> keys = new ArrayList<>(qProfiles.findAll().size()); + + for (QProfile qp : qProfiles.findAll()) { + keys.add(qp.getKey()); + } + + return keys; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/DefaultActiveRulesLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/DefaultActiveRulesLoader.java new file mode 100644 index 00000000000..9485d99e1cb --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/DefaultActiveRulesLoader.java @@ -0,0 +1,136 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.rule; + +import org.sonar.batch.util.BatchUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.mutable.MutableBoolean; +import org.sonar.api.rule.RuleKey; +import org.sonar.batch.cache.WSLoader; +import org.sonar.batch.cache.WSLoaderResult; +import org.sonarqube.ws.Rules.Active; +import org.sonarqube.ws.Rules.Active.Param; +import org.sonarqube.ws.Rules.ActiveList; +import org.sonarqube.ws.Rules.Rule; +import org.sonarqube.ws.Rules.SearchResponse; + +public class DefaultActiveRulesLoader implements ActiveRulesLoader { + private static final String RULES_SEARCH_URL = "/api/rules/search.protobuf?f=repo,name,severity,lang,internalKey,templateKey,params,actives&activation=true"; + + private final WSLoader wsLoader; + + public DefaultActiveRulesLoader(WSLoader wsLoader) { + this.wsLoader = wsLoader; + } + + @Override + public List<LoadedActiveRule> load(String qualityProfileKey, @Nullable MutableBoolean fromCache) { + List<LoadedActiveRule> ruleList = new LinkedList<>(); + int page = 1; + int pageSize = 500; + int loaded = 0; + + while (true) { + WSLoaderResult<InputStream> result = wsLoader.loadStream(getUrl(qualityProfileKey, page, pageSize)); + SearchResponse response = loadFromStream(result.get()); + List<LoadedActiveRule> pageRules = readPage(response); + ruleList.addAll(pageRules); + loaded += response.getPs(); + + if (response.getTotal() <= loaded) { + break; + } + page++; + if (fromCache != null) { + fromCache.setValue(result.isFromCache()); + } + } + + return ruleList; + } + + private static String getUrl(String qualityProfileKey, int page, int pageSize) { + StringBuilder builder = new StringBuilder(1024); + builder.append(RULES_SEARCH_URL); + builder.append("&qprofile=").append(BatchUtils.encodeForUrl(qualityProfileKey)); + builder.append("&p=").append(page); + builder.append("&ps=").append(pageSize); + return builder.toString(); + } + + private static SearchResponse loadFromStream(InputStream is) { + try { + return SearchResponse.parseFrom(is); + } catch (IOException e) { + throw new IllegalStateException("Failed to load quality profiles", e); + } finally { + IOUtils.closeQuietly(is); + } + } + + private static List<LoadedActiveRule> readPage(SearchResponse response) { + List<LoadedActiveRule> loadedRules = new LinkedList<>(); + + List<Rule> rulesList = response.getRulesList(); + Map<String, ActiveList> actives = response.getActives().getActives(); + + for (Rule r : rulesList) { + ActiveList activeList = actives.get(r.getKey()); + Active active = activeList.getActiveList(0); + + LoadedActiveRule loadedRule = new LoadedActiveRule(); + + loadedRule.setRuleKey(RuleKey.parse(r.getKey())); + loadedRule.setName(r.getName()); + loadedRule.setSeverity(active.getSeverity()); + loadedRule.setLanguage(r.getLang()); + loadedRule.setInternalKey(r.getInternalKey()); + if (r.hasTemplateKey()) { + RuleKey templateRuleKey = RuleKey.parse(r.getTemplateKey()); + loadedRule.setTemplateRuleKey(templateRuleKey.rule()); + } + + Map<String, String> params = new HashMap<>(); + + for (org.sonarqube.ws.Rules.Rule.Param param : r.getParams().getParamsList()) { + params.put(param.getKey(), param.getDefaultValue()); + } + + // overrides defaultValue if the key is the same + for (Param param : active.getParamsList()) { + params.put(param.getKey(), param.getValue()); + } + loadedRule.setParams(params); + loadedRules.add(loadedRule); + } + + return loadedRules; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/DefaultRulesLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/DefaultRulesLoader.java new file mode 100644 index 00000000000..62292b4581b --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/DefaultRulesLoader.java @@ -0,0 +1,66 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.rule; + +import org.apache.commons.io.IOUtils; + +import org.sonar.batch.cache.WSLoaderResult; +import org.sonar.batch.cache.WSLoader; + +import javax.annotation.Nullable; + +import org.apache.commons.lang.mutable.MutableBoolean; +import org.sonarqube.ws.Rules.ListResponse.Rule; +import org.sonarqube.ws.Rules.ListResponse; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +public class DefaultRulesLoader implements RulesLoader { + private static final String RULES_SEARCH_URL = "/api/rules/list.protobuf"; + + private final WSLoader wsLoader; + + public DefaultRulesLoader(WSLoader wsLoader) { + this.wsLoader = wsLoader; + } + + @Override + public List<Rule> load(@Nullable MutableBoolean fromCache) { + WSLoaderResult<InputStream> result = wsLoader.loadStream(RULES_SEARCH_URL); + ListResponse list = loadFromStream(result.get()); + if (fromCache != null) { + fromCache.setValue(result.isFromCache()); + } + return list.getRulesList(); + } + + private static ListResponse loadFromStream(InputStream is) { + try { + return ListResponse.parseFrom(is); + } catch (IOException e) { + throw new IllegalStateException("Unable to get rules", e); + } finally { + IOUtils.closeQuietly(is); + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/LoadedActiveRule.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/LoadedActiveRule.java new file mode 100644 index 00000000000..31e5919220f --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/LoadedActiveRule.java @@ -0,0 +1,93 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.rule; + +import java.util.Map; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.sonar.api.rule.RuleKey; + +public class LoadedActiveRule { + private RuleKey ruleKey; + private String severity; + private String name; + private String language; + private Map<String, String> params; + private String templateRuleKey; + private String internalKey; + + public RuleKey getRuleKey() { + return ruleKey; + } + + public void setRuleKey(RuleKey ruleKey) { + this.ruleKey = ruleKey; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSeverity() { + return severity; + } + + public void setSeverity(String severity) { + this.severity = severity; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public Map<String, String> getParams() { + return params; + } + + public void setParams(Map<String, String> params) { + this.params = params; + } + + @CheckForNull + public String getTemplateRuleKey() { + return templateRuleKey; + } + + public void setTemplateRuleKey(@Nullable String templateRuleKey) { + this.templateRuleKey = templateRuleKey; + } + + public String getInternalKey() { + return internalKey; + } + + public void setInternalKey(String internalKey) { + this.internalKey = internalKey; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/ModuleQProfiles.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/ModuleQProfiles.java new file mode 100644 index 00000000000..5a671de840f --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/ModuleQProfiles.java @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.rule; + +import org.sonar.api.utils.DateUtils; + +import org.sonarqube.ws.QualityProfiles.SearchWsResponse.QualityProfile; +import com.google.common.collect.ImmutableMap; +import org.sonar.api.batch.BatchSide; + +import javax.annotation.CheckForNull; + +import java.util.Collection; +import java.util.Map; + +/** + * Lists the Quality profiles enabled on the current module. + */ +@BatchSide +public class ModuleQProfiles { + + public static final String SONAR_PROFILE_PROP = "sonar.profile"; + private final Map<String, QProfile> byLanguage; + + public ModuleQProfiles(Collection<QualityProfile> profiles) { + ImmutableMap.Builder<String, QProfile> builder = ImmutableMap.builder(); + + for (QualityProfile qProfile : profiles) { + builder.put(qProfile.getLanguage(), + new QProfile() + .setKey(qProfile.getKey()) + .setName(qProfile.getName()) + .setLanguage(qProfile.getLanguage()) + .setRulesUpdatedAt(DateUtils.parseDateTime(qProfile.getRulesUpdatedAt()))); + } + byLanguage = builder.build(); + } + + public Collection<QProfile> findAll() { + return byLanguage.values(); + } + + @CheckForNull + public QProfile findByLanguage(String language) { + return byLanguage.get(language); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/QProfile.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/QProfile.java new file mode 100644 index 00000000000..5abc9955c7d --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/QProfile.java @@ -0,0 +1,96 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.rule; + +import com.google.common.base.Objects; + +import java.util.Date; + +public class QProfile { + + private String key; + private String name; + private String language; + private Date rulesUpdatedAt; + + public String getKey() { + return key; + } + + public QProfile setKey(String key) { + this.key = key; + return this; + } + + public String getName() { + return name; + } + + public QProfile setName(String name) { + this.name = name; + return this; + } + + public String getLanguage() { + return language; + } + + public QProfile setLanguage(String language) { + this.language = language; + return this; + } + + public Date getRulesUpdatedAt() { + return rulesUpdatedAt; + } + + public QProfile setRulesUpdatedAt(Date d) { + this.rulesUpdatedAt = d; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + QProfile qProfile = (QProfile) o; + return key.equals(qProfile.key); + } + + @Override + public int hashCode() { + return key.hashCode(); + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("key", key) + .add("name", name) + .add("language", language) + .add("rulesUpdatedAt", rulesUpdatedAt) + .toString(); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/QProfileSensor.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/QProfileSensor.java new file mode 100644 index 00000000000..bdffd6f1f5d --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/QProfileSensor.java @@ -0,0 +1,72 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.rule; + +import org.sonar.api.batch.AnalysisMode; +import org.sonar.api.batch.Sensor; +import org.sonar.api.batch.SensorContext; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.resources.Project; + +/** + * Stores which Quality profiles have been used on the current module. + * + * TODO This information should not be stored as a measure but should be send as metadata in the {@link org.sonar.scanner.protocol.output.ScannerReport} + */ +public class QProfileSensor implements Sensor { + + private final ModuleQProfiles moduleQProfiles; + private final FileSystem fs; + private final AnalysisMode analysisMode; + + public QProfileSensor(ModuleQProfiles moduleQProfiles, FileSystem fs, AnalysisMode analysisMode) { + this.moduleQProfiles = moduleQProfiles; + this.fs = fs; + this.analysisMode = analysisMode; + } + + @Override + public boolean shouldExecuteOnProject(Project project) { + // Should be only executed on leaf modules + return project.getModules().isEmpty() + // Useless in issues mode + && !analysisMode.isIssues(); + } + + @Override + public void analyse(Project project, SensorContext context) { + UsedQProfiles used = new UsedQProfiles(); + for (String language : fs.languages()) { + QProfile profile = moduleQProfiles.findByLanguage(language); + if (profile != null) { + used.add(profile); + } + } + Measure<?> detailsMeasure = new Measure<>(CoreMetrics.QUALITY_PROFILES, used.toJson()); + context.saveMeasure(detailsMeasure); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/QProfileVerifier.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/QProfileVerifier.java new file mode 100644 index 00000000000..6206a289f17 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/QProfileVerifier.java @@ -0,0 +1,71 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.rule; + +import com.google.common.annotations.VisibleForTesting; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.config.Settings; +import org.sonar.api.utils.MessageException; + +import static org.apache.commons.lang.StringUtils.isNotEmpty; + +@BatchSide +public class QProfileVerifier { + + private static final Logger LOG = LoggerFactory.getLogger(QProfileVerifier.class); + + private final Settings settings; + private final FileSystem fs; + private final ModuleQProfiles profiles; + + public QProfileVerifier(Settings settings, FileSystem fs, ModuleQProfiles profiles) { + this.settings = settings; + this.fs = fs; + this.profiles = profiles; + } + + public void execute() { + execute(LOG); + } + + @VisibleForTesting + void execute(Logger logger) { + String defaultName = settings.getString(ModuleQProfiles.SONAR_PROFILE_PROP); + boolean defaultNameUsed = StringUtils.isBlank(defaultName); + for (String lang : fs.languages()) { + QProfile profile = profiles.findByLanguage(lang); + if (profile == null) { + logger.warn("No Quality profile found for language " + lang); + } else { + logger.info("Quality profile for {}: {}", lang, profile.getName()); + if (isNotEmpty(defaultName) && defaultName.equals(profile.getName())) { + defaultNameUsed = true; + } + } + } + if (!defaultNameUsed && !fs.languages().isEmpty()) { + throw MessageException.of("sonar.profile was set to '" + defaultName + "' but didn't match any profile for any language. Please check your configuration."); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/RuleFinderCompatibility.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/RuleFinderCompatibility.java new file mode 100644 index 00000000000..fe763f873bd --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/RuleFinderCompatibility.java @@ -0,0 +1,123 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.rule; + +import org.sonar.api.batch.rule.Rules; + +import com.google.common.base.Function; +import com.google.common.collect.Collections2; +import org.apache.commons.lang.builder.ReflectionToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rules.Rule; +import org.sonar.api.rules.RuleFinder; +import org.sonar.api.rules.RuleQuery; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +/** + * FIXME Waiting for the list of all server rules on batch side this is implemented by redirecting on ActiveRules. This is not correct + * since there is a difference between a rule that doesn't exists and a rule that is not activated in project quality profile. + * + */ +public class RuleFinderCompatibility implements RuleFinder { + + private final Rules rules; + private static Function<org.sonar.api.batch.rule.Rule, Rule> ruleTransformer = new Function<org.sonar.api.batch.rule.Rule, Rule>() { + @Override + public Rule apply(@Nonnull org.sonar.api.batch.rule.Rule input) { + return toRule(input); + } + }; + + public RuleFinderCompatibility(Rules rules) { + this.rules = rules; + } + + @Override + public Rule findById(int ruleId) { + throw new UnsupportedOperationException("Unable to find rule by id"); + } + + @Override + public Rule findByKey(String repositoryKey, String key) { + return findByKey(RuleKey.of(repositoryKey, key)); + } + + @Override + public Rule findByKey(RuleKey key) { + return toRule(rules.find(key)); + } + + @Override + public Rule find(RuleQuery query) { + Collection<Rule> all = findAll(query); + if (all.size() > 1) { + throw new IllegalArgumentException("Non unique result for rule query: " + ReflectionToStringBuilder.toString(query, ToStringStyle.SHORT_PREFIX_STYLE)); + } else if (all.isEmpty()) { + return null; + } else { + return all.iterator().next(); + } + } + + @Override + public Collection<Rule> findAll(RuleQuery query) { + if (query.getConfigKey() != null) { + if (query.getRepositoryKey() != null && query.getKey() == null) { + return byInternalKey(query); + } + } else if (query.getRepositoryKey() != null) { + if (query.getKey() != null) { + return byKey(query); + } else { + return byRepository(query); + } + } + throw new UnsupportedOperationException("Unable to find rule by query"); + } + + private Collection<Rule> byRepository(RuleQuery query) { + return Collections2.transform(rules.findByRepository(query.getRepositoryKey()), ruleTransformer); + } + + + + private Collection<Rule> byKey(RuleQuery query) { + Rule rule = toRule(rules.find(RuleKey.of(query.getRepositoryKey(), query.getKey()))); + return rule != null ? Arrays.asList(rule) : Collections.<Rule>emptyList(); + } + + private Collection<Rule> byInternalKey(RuleQuery query) { + return Collections2.transform(rules.findByInternalKey(query.getRepositoryKey(), query.getConfigKey()), ruleTransformer); + } + + @CheckForNull + private static Rule toRule(@Nullable org.sonar.api.batch.rule.Rule ar) { + return ar == null ? null : Rule.create(ar.key().repository(), ar.key().rule()).setName(ar.name()).setConfigKey(ar.internalKey()); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/RulesLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/RulesLoader.java new file mode 100644 index 00000000000..9572f628f2d --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/RulesLoader.java @@ -0,0 +1,29 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.rule; + +import java.util.List; +import javax.annotation.Nullable; +import org.apache.commons.lang.mutable.MutableBoolean; +import org.sonarqube.ws.Rules.ListResponse.Rule; + +public interface RulesLoader { + List<Rule> load(@Nullable MutableBoolean fromCache); +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/RulesProfileProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/RulesProfileProvider.java new file mode 100644 index 00000000000..de29cc352fc --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/RulesProfileProvider.java @@ -0,0 +1,95 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.rule; + +import com.google.common.collect.Lists; +import java.util.Collection; +import java.util.Map; +import org.apache.commons.lang.StringUtils; +import org.picocontainer.injectors.ProviderAdapter; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.config.Settings; +import org.sonar.api.profiles.RulesProfile; +import org.sonar.api.rules.ActiveRule; +import org.sonar.api.rules.Rule; +import org.sonar.api.rules.RulePriority; + +/** + * Ensures backward-compatibility with extensions that use {@link org.sonar.api.profiles.RulesProfile}. + */ +public class RulesProfileProvider extends ProviderAdapter { + + private RulesProfile singleton = null; + + public RulesProfile provide(ModuleQProfiles qProfiles, ActiveRules activeRules, Settings settings) { + if (singleton == null) { + String lang = settings.getString(CoreProperties.PROJECT_LANGUAGE_PROPERTY); + if (StringUtils.isNotBlank(lang)) { + // Backward-compatibility with single-language modules + singleton = loadSingleLanguageProfile(qProfiles, activeRules, lang); + } else { + singleton = loadProfiles(qProfiles, activeRules); + } + } + return singleton; + } + + private static RulesProfile loadSingleLanguageProfile(ModuleQProfiles qProfiles, ActiveRules activeRules, String language) { + QProfile qProfile = qProfiles.findByLanguage(language); + if (qProfile != null) { + return new RulesProfileWrapper(select(qProfile, activeRules)); + } + return new RulesProfileWrapper(Lists.<RulesProfile>newArrayList()); + } + + private static RulesProfile loadProfiles(ModuleQProfiles qProfiles, ActiveRules activeRules) { + Collection<RulesProfile> dtos = Lists.newArrayList(); + for (QProfile qProfile : qProfiles.findAll()) { + dtos.add(select(qProfile, activeRules)); + } + return new RulesProfileWrapper(dtos); + } + + private static RulesProfile select(QProfile qProfile, ActiveRules activeRules) { + RulesProfile deprecatedProfile = new RulesProfile(); + // TODO deprecatedProfile.setVersion(qProfile.version()); + deprecatedProfile.setName(qProfile.getName()); + deprecatedProfile.setLanguage(qProfile.getLanguage()); + for (org.sonar.api.batch.rule.ActiveRule activeRule : activeRules.findByLanguage(qProfile.getLanguage())) { + Rule rule = Rule.create(activeRule.ruleKey().repository(), activeRule.ruleKey().rule()); + rule.setConfigKey(activeRule.internalKey()); + + // SONAR-6706 + if (activeRule.templateRuleKey() != null) { + rule.setTemplate(Rule.create(activeRule.ruleKey().repository(), activeRule.templateRuleKey())); + } + + ActiveRule deprecatedActiveRule = deprecatedProfile.activateRule(rule, + RulePriority.valueOf(activeRule.severity())); + for (Map.Entry<String, String> param : activeRule.params().entrySet()) { + rule.createParameter(param.getKey()); + deprecatedActiveRule.setParameter(param.getKey(), param.getValue()); + } + } + return deprecatedProfile; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/RulesProfileWrapper.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/RulesProfileWrapper.java new file mode 100644 index 00000000000..327c141ea34 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/RulesProfileWrapper.java @@ -0,0 +1,140 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.rule; + +import com.google.common.collect.Lists; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.profiles.RulesProfile; +import org.sonar.api.rules.ActiveRule; +import org.sonar.api.rules.Rule; +import org.sonar.api.utils.SonarException; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * This wrapper is used to try to preserve backward compatibility for plugins that used to + * depends on {@link org.sonar.api.profiles.RulesProfile} + * + * @since 4.2 + */ +public class RulesProfileWrapper extends RulesProfile { + + private static final Logger LOG = LoggerFactory.getLogger(RulesProfileWrapper.class); + private static final String DEPRECATED_USAGE_MESSAGE = "Please update your plugin to support multi-language analysis"; + + private final Collection<RulesProfile> profiles; + private final RulesProfile singleLanguageProfile; + + public RulesProfileWrapper(Collection<RulesProfile> profiles) { + this.profiles = profiles; + this.singleLanguageProfile = null; + } + + public RulesProfileWrapper(RulesProfile profile) { + this.profiles = Lists.newArrayList(profile); + this.singleLanguageProfile = profile; + } + + @Override + public Integer getId() { + return getSingleProfileOrFail().getId(); + } + + private RulesProfile getSingleProfileOrFail() { + if (singleLanguageProfile == null) { + throw new IllegalStateException(DEPRECATED_USAGE_MESSAGE); + } + return singleLanguageProfile; + } + + @Override + public String getName() { + return singleLanguageProfile != null ? singleLanguageProfile.getName() : "SonarQube"; + } + + @Override + public String getLanguage() { + if (singleLanguageProfile == null) { + // Multi-languages module + // This is a hack for CommonChecksDecorator that call this method in its constructor + LOG.debug(DEPRECATED_USAGE_MESSAGE, new SonarException(DEPRECATED_USAGE_MESSAGE)); + return ""; + } + return singleLanguageProfile.getLanguage(); + } + + @Override + public List<ActiveRule> getActiveRules() { + List<ActiveRule> activeRules = new ArrayList<>(); + for (RulesProfile profile : profiles) { + activeRules.addAll(profile.getActiveRules()); + } + return activeRules; + } + + @Override + public ActiveRule getActiveRule(String repositoryKey, String ruleKey) { + for (RulesProfile profile : profiles) { + ActiveRule activeRule = profile.getActiveRule(repositoryKey, ruleKey); + if (activeRule != null) { + return activeRule; + } + } + return null; + } + + @Override + public List<ActiveRule> getActiveRulesByRepository(String repositoryKey) { + List<ActiveRule> activeRules = new ArrayList<>(); + for (RulesProfile profile : profiles) { + activeRules.addAll(profile.getActiveRulesByRepository(repositoryKey)); + } + return activeRules; + } + + @Override + public List<ActiveRule> getActiveRules(boolean acceptDisabledRules) { + List<ActiveRule> activeRules = new ArrayList<>(); + for (RulesProfile profile : profiles) { + activeRules.addAll(profile.getActiveRules(acceptDisabledRules)); + } + return activeRules; + } + + @Override + public ActiveRule getActiveRule(Rule rule) { + for (RulesProfile profile : profiles) { + ActiveRule activeRule = profile.getActiveRule(rule); + if (activeRule != null) { + return activeRule; + } + } + return null; + } + + @Override + public Boolean getDefaultProfile() { + return getSingleProfileOrFail().getDefaultProfile(); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/RulesProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/RulesProvider.java new file mode 100644 index 00000000000..7a3e0ea34f0 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/RulesProvider.java @@ -0,0 +1,65 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.rule; + +import org.apache.commons.lang.mutable.MutableBoolean; + +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.api.utils.log.Profiler; + +import java.util.List; + +import org.sonarqube.ws.Rules.ListResponse.Rule; +import org.picocontainer.injectors.ProviderAdapter; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.batch.rule.internal.RulesBuilder; +import org.sonar.api.batch.rule.internal.NewRule; +import org.sonar.api.batch.rule.Rules; + +public class RulesProvider extends ProviderAdapter { + private static final Logger LOG = Loggers.get(RulesProvider.class); + private static final String LOG_MSG = "Load server rules"; + private Rules singleton = null; + + public Rules provide(RulesLoader ref) { + if (singleton == null) { + singleton = load(ref); + } + return singleton; + } + + private static Rules load(RulesLoader ref) { + Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG); + MutableBoolean fromCache = new MutableBoolean(); + List<Rule> loadedRules = ref.load(fromCache); + RulesBuilder builder = new RulesBuilder(); + + for (Rule r : loadedRules) { + NewRule newRule = builder.add(RuleKey.of(r.getRepository(), r.getKey())); + newRule.setName(r.getName()); + newRule.setInternalKey(r.getInternalKey()); + } + + profiler.stopInfo(fromCache.booleanValue()); + + return builder.build(); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/UsedQProfiles.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/UsedQProfiles.java new file mode 100644 index 00000000000..72186a5e959 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/UsedQProfiles.java @@ -0,0 +1,112 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.rule; + +import com.google.common.collect.Sets; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.sonar.api.utils.text.JsonWriter; +import org.sonar.core.util.UtcDateUtils; + +import javax.annotation.concurrent.Immutable; + +import java.io.StringWriter; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.SortedSet; + +@Immutable +public class UsedQProfiles { + + private final SortedSet<QProfile> profiles = Sets.newTreeSet(new Comparator<QProfile>() { + @Override + public int compare(QProfile o1, QProfile o2) { + int c = o1.getLanguage().compareTo(o2.getLanguage()); + if (c == 0) { + c = o1.getName().compareTo(o2.getName()); + } + return c; + } + }); + + public static UsedQProfiles fromJson(String json) { + UsedQProfiles result = new UsedQProfiles(); + JsonArray jsonRoot = new JsonParser().parse(json).getAsJsonArray(); + for (JsonElement jsonElt : jsonRoot) { + JsonObject jsonProfile = jsonElt.getAsJsonObject(); + QProfile profile = new QProfile(); + profile.setKey(jsonProfile.get("key").getAsString()); + profile.setName(jsonProfile.get("name").getAsString()); + profile.setLanguage(jsonProfile.get("language").getAsString()); + profile.setRulesUpdatedAt(UtcDateUtils.parseDateTime(jsonProfile.get("rulesUpdatedAt").getAsString())); + result.add(profile); + } + return result; + } + + public String toJson() { + StringWriter json = new StringWriter(); + JsonWriter writer = JsonWriter.of(json); + writer.beginArray(); + for (QProfile profile : profiles) { + writer + .beginObject() + .prop("key", profile.getKey()) + .prop("language", profile.getLanguage()) + .prop("name", profile.getName()) + .prop("rulesUpdatedAt", UtcDateUtils.formatDateTime(profile.getRulesUpdatedAt())) + .endObject(); + } + writer.endArray(); + writer.close(); + return json.toString(); + } + + public UsedQProfiles add(UsedQProfiles other) { + addAll(other.profiles); + return this; + } + + public UsedQProfiles add(QProfile profile) { + profiles.add(profile); + return this; + } + + public UsedQProfiles addAll(Collection<QProfile> profiles) { + this.profiles.addAll(profiles); + return this; + } + + public SortedSet<QProfile> profiles() { + return profiles; + } + + public Map<String, QProfile> profilesByKey() { + Map<String, QProfile> map = new HashMap<>(); + for (QProfile profile : profiles) { + map.put(profile.getKey(), profile); + } + return map; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/package-info.java new file mode 100644 index 00000000000..1bdc402f399 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/rule/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.rule; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ImmutableProjectReactor.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ImmutableProjectReactor.java new file mode 100644 index 00000000000..4bfa99c6da2 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ImmutableProjectReactor.java @@ -0,0 +1,71 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import javax.annotation.CheckForNull; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.bootstrap.ProjectDefinition; + +/** + * Immutable copy of project reactor after all modifications have been applied (see {@link ImmutableProjectReactorProvider}). + */ +@BatchSide +public class ImmutableProjectReactor { + + private ProjectDefinition root; + private Map<String, ProjectDefinition> byKey = new LinkedHashMap<>(); + + public ImmutableProjectReactor(ProjectDefinition root) { + if (root.getParent() != null) { + throw new IllegalArgumentException("Not a root project: " + root); + } + this.root = root; + collectProjects(root); + } + + public Collection<ProjectDefinition> getProjects() { + return byKey.values(); + } + + /** + * Populates list of projects from hierarchy. + */ + private void collectProjects(ProjectDefinition def) { + if (byKey.containsKey(def.getKeyWithBranch())) { + throw new IllegalStateException("Duplicate module key in reactor: " + def.getKeyWithBranch()); + } + byKey.put(def.getKeyWithBranch(), def); + for (ProjectDefinition child : def.getSubProjects()) { + collectProjects(child); + } + } + + public ProjectDefinition getRoot() { + return root; + } + + @CheckForNull + public ProjectDefinition getProjectDefinition(String keyWithBranch) { + return byKey.get(keyWithBranch); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ImmutableProjectReactorProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ImmutableProjectReactorProvider.java new file mode 100644 index 00000000000..ecf58a88482 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ImmutableProjectReactorProvider.java @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan; + +import org.picocontainer.injectors.ProviderAdapter; +import org.sonar.api.batch.bootstrap.ProjectReactor; + +public class ImmutableProjectReactorProvider extends ProviderAdapter { + + private ImmutableProjectReactor singleton; + + public ImmutableProjectReactor provide(ProjectReactor reactor, ProjectBuildersExecutor projectBuildersExecutor, ProjectExclusions exclusions, ProjectReactorValidator validator) { + if (singleton == null) { + // 1 Apply project builders + projectBuildersExecutor.execute(reactor); + + // 2 Apply project exclusions + exclusions.apply(reactor); + + // 3 Validate final reactor + validator.validate(reactor); + + singleton = new ImmutableProjectReactor(reactor.getRoot()); + } + return singleton; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/LanguageVerifier.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/LanguageVerifier.java new file mode 100644 index 00000000000..83e27324e64 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/LanguageVerifier.java @@ -0,0 +1,69 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan; + +import org.sonar.batch.repository.language.Language; +import org.sonar.batch.repository.language.LanguagesRepository; +import org.apache.commons.lang.StringUtils; +import org.picocontainer.Startable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.fs.internal.DefaultFileSystem; +import org.sonar.api.config.Settings; +import org.sonar.api.utils.MessageException; + +/** + * Verifies that the property sonar.language is valid + */ +public class LanguageVerifier implements Startable { + + private static final Logger LOG = LoggerFactory.getLogger(LanguageVerifier.class); + + private final Settings settings; + private final LanguagesRepository languages; + private final DefaultFileSystem fs; + + public LanguageVerifier(Settings settings, LanguagesRepository languages, DefaultFileSystem fs) { + this.settings = settings; + this.languages = languages; + this.fs = fs; + } + + @Override + public void start() { + String languageKey = settings.getString(CoreProperties.PROJECT_LANGUAGE_PROPERTY); + if (StringUtils.isNotBlank(languageKey)) { + LOG.info("Language is forced to {}", languageKey); + Language language = languages.get(languageKey); + if (language == null) { + throw MessageException.of("You must install a plugin that supports the language '" + languageKey + "'"); + } + + // force the registration of the language, even if there are no related source files + fs.addLanguages(languageKey); + } + } + + @Override + public void stop() { + // nothing to do + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java new file mode 100644 index 00000000000..56da7fd624d --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java @@ -0,0 +1,197 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.api.batch.InstantiationStrategy; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.fs.internal.FileMetadata; +import org.sonar.api.batch.rule.CheckFactory; +import org.sonar.api.resources.Project; +import org.sonar.api.scan.filesystem.FileExclusions; +import org.sonar.batch.DefaultProjectTree; +import org.sonar.batch.bootstrap.BatchExtensionDictionnary; +import org.sonar.batch.bootstrap.ExtensionInstaller; +import org.sonar.batch.bootstrap.ExtensionMatcher; +import org.sonar.batch.bootstrap.ExtensionUtils; +import org.sonar.batch.deprecated.DeprecatedSensorContext; +import org.sonar.batch.deprecated.perspectives.BatchPerspectives; +import org.sonar.batch.events.EventBus; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.index.DefaultIndex; +import org.sonar.batch.issue.IssuableFactory; +import org.sonar.batch.issue.IssueFilters; +import org.sonar.batch.issue.ModuleIssues; +import org.sonar.batch.issue.ignore.EnforceIssuesFilter; +import org.sonar.batch.issue.ignore.IgnoreIssuesFilter; +import org.sonar.batch.issue.ignore.pattern.IssueExclusionPatternInitializer; +import org.sonar.batch.issue.ignore.pattern.IssueInclusionPatternInitializer; +import org.sonar.batch.issue.ignore.scanner.IssueExclusionsLoader; +import org.sonar.batch.issue.ignore.scanner.IssueExclusionsRegexpScanner; +import org.sonar.batch.phases.AbstractPhaseExecutor; +import org.sonar.batch.phases.InitializersExecutor; +import org.sonar.batch.phases.IssuesPhaseExecutor; +import org.sonar.batch.phases.PostJobsExecutor; +import org.sonar.batch.phases.ProjectInitializer; +import org.sonar.batch.phases.PublishPhaseExecutor; +import org.sonar.batch.phases.SensorsExecutor; +import org.sonar.batch.postjob.DefaultPostJobContext; +import org.sonar.batch.postjob.PostJobOptimizer; +import org.sonar.batch.rule.QProfileSensor; +import org.sonar.batch.rule.QProfileVerifier; +import org.sonar.batch.rule.RuleFinderCompatibility; +import org.sonar.batch.rule.RulesProfileProvider; +import org.sonar.batch.scan.filesystem.ComponentIndexer; +import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem; +import org.sonar.batch.scan.filesystem.DeprecatedFileFilters; +import org.sonar.batch.scan.filesystem.ExclusionFilters; +import org.sonar.batch.scan.filesystem.FileIndexer; +import org.sonar.batch.scan.filesystem.FileSystemLogger; +import org.sonar.batch.scan.filesystem.InputFileBuilderFactory; +import org.sonar.batch.scan.filesystem.LanguageDetectionFactory; +import org.sonar.batch.scan.filesystem.ModuleFileSystemInitializer; +import org.sonar.batch.scan.filesystem.ModuleInputFileCache; +import org.sonar.batch.scan.filesystem.StatusDetectionFactory; +import org.sonar.batch.scan.report.IssuesReports; +import org.sonar.batch.sensor.DefaultSensorStorage; +import org.sonar.batch.sensor.SensorOptimizer; +import org.sonar.batch.sensor.coverage.CoverageExclusions; +import org.sonar.batch.source.HighlightableBuilder; +import org.sonar.batch.source.SymbolizableBuilder; +import org.sonar.core.platform.ComponentContainer; + +public class ModuleScanContainer extends ComponentContainer { + private static final Logger LOG = LoggerFactory.getLogger(ModuleScanContainer.class); + private final Project module; + + public ModuleScanContainer(ProjectScanContainer parent, Project module) { + super(parent); + this.module = module; + } + + @Override + protected void doBeforeStart() { + LOG.info("------------- Scan {}", module.getName()); + addCoreComponents(); + addExtensions(); + } + + private void addCoreComponents() { + ProjectDefinition moduleDefinition = getComponentByType(DefaultProjectTree.class).getProjectDefinition(module); + add( + moduleDefinition, + module, + getComponentByType(BatchComponentCache.class).get(module).inputComponent(), + ModuleSettings.class); + + // hack to initialize settings before ExtensionProviders + ModuleSettings moduleSettings = getComponentByType(ModuleSettings.class); + module.setSettings(moduleSettings); + + if (getComponentByType(AnalysisMode.class).isIssues()) { + add(IssuesPhaseExecutor.class, + IssuesReports.class); + } else { + add(PublishPhaseExecutor.class); + } + + add( + EventBus.class, + RuleFinderCompatibility.class, + PostJobsExecutor.class, + SensorsExecutor.class, + InitializersExecutor.class, + ProjectInitializer.class, + + // file system + ModuleInputFileCache.class, + FileExclusions.class, + ExclusionFilters.class, + DeprecatedFileFilters.class, + InputFileBuilderFactory.class, + FileMetadata.class, + StatusDetectionFactory.class, + LanguageDetectionFactory.class, + FileIndexer.class, + ComponentIndexer.class, + LanguageVerifier.class, + FileSystemLogger.class, + DefaultModuleFileSystem.class, + ModuleFileSystemInitializer.class, + QProfileVerifier.class, + + SensorOptimizer.class, + PostJobOptimizer.class, + + DefaultPostJobContext.class, + DefaultSensorStorage.class, + DeprecatedSensorContext.class, + BatchExtensionDictionnary.class, + IssueFilters.class, + CoverageExclusions.class, + + // rules + new RulesProfileProvider(), + QProfileSensor.class, + CheckFactory.class, + + // issues + IssuableFactory.class, + ModuleIssues.class, + org.sonar.api.issue.NoSonarFilter.class, + + // issue exclusions + IssueInclusionPatternInitializer.class, + IssueExclusionPatternInitializer.class, + IssueExclusionsRegexpScanner.class, + IssueExclusionsLoader.class, + EnforceIssuesFilter.class, + IgnoreIssuesFilter.class, + + // Perspectives + BatchPerspectives.class, + HighlightableBuilder.class, + SymbolizableBuilder.class); + } + + private void addExtensions() { + ExtensionInstaller installer = getComponentByType(ExtensionInstaller.class); + installer.install(this, new ExtensionMatcher() { + @Override + public boolean accept(Object extension) { + return ExtensionUtils.isBatchSide(extension) && ExtensionUtils.isInstantiationStrategy(extension, InstantiationStrategy.PER_PROJECT); + } + }); + } + + @Override + protected void doAfterStart() { + DefaultIndex index = getComponentByType(DefaultIndex.class); + index.setCurrentProject(module, getComponentByType(DefaultSensorStorage.class)); + + getComponentByType(AbstractPhaseExecutor.class).execute(module); + + // Free memory since module settings are no more used + module.setSettings(null); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ModuleSettings.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ModuleSettings.java new file mode 100644 index 00000000000..2176ad347fe --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ModuleSettings.java @@ -0,0 +1,96 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan; + +import com.google.common.collect.Lists; +import java.util.List; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.config.Settings; +import org.sonar.api.utils.MessageException; +import org.sonar.batch.analysis.DefaultAnalysisMode; +import org.sonar.batch.bootstrap.GlobalSettings; +import org.sonar.batch.report.AnalysisContextReportPublisher; +import org.sonar.batch.repository.ProjectRepositories; + +/** + * @since 2.12 + */ +public class ModuleSettings extends Settings { + + private final ProjectRepositories projectRepos; + private final DefaultAnalysisMode analysisMode; + + public ModuleSettings(GlobalSettings batchSettings, ProjectDefinition moduleDefinition, ProjectRepositories projectSettingsRepo, + DefaultAnalysisMode analysisMode, AnalysisContextReportPublisher contextReportPublisher) { + super(batchSettings.getDefinitions()); + this.projectRepos = projectSettingsRepo; + this.analysisMode = analysisMode; + getEncryption().setPathToSecretKey(batchSettings.getString(CoreProperties.ENCRYPTION_SECRET_KEY_PATH)); + + init(moduleDefinition, batchSettings); + contextReportPublisher.dumpSettings(moduleDefinition); + } + + private ModuleSettings init(ProjectDefinition moduleDefinition, GlobalSettings batchSettings) { + addProjectProperties(moduleDefinition, batchSettings); + addBuildProperties(moduleDefinition); + return this; + } + + private void addProjectProperties(ProjectDefinition def, GlobalSettings batchSettings) { + addProperties(batchSettings.getProperties()); + do { + if (projectRepos.moduleExists(def.getKeyWithBranch())) { + addProperties(projectRepos.settings(def.getKeyWithBranch())); + break; + } + def = def.getParent(); + } while (def != null); + } + + private void addBuildProperties(ProjectDefinition project) { + List<ProjectDefinition> orderedProjects = getTopDownParentProjects(project); + for (ProjectDefinition p : orderedProjects) { + addProperties(p.properties()); + } + } + + /** + * From root to given project + */ + static List<ProjectDefinition> getTopDownParentProjects(ProjectDefinition project) { + List<ProjectDefinition> result = Lists.newArrayList(); + ProjectDefinition p = project; + while (p != null) { + result.add(0, p); + p = p.getParent(); + } + return result; + } + + @Override + protected void doOnGetProperties(String key) { + if (analysisMode.isIssues() && key.endsWith(".secured") && !key.contains(".license")) { + throw MessageException.of("Access to the secured property '" + key + + "' is not possible in issues mode. The SonarQube plugin which requires this property must be deactivated in issues mode."); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/MutableProjectReactorProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/MutableProjectReactorProvider.java new file mode 100644 index 00000000000..afde1e18c4d --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/MutableProjectReactorProvider.java @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan; + +import org.picocontainer.injectors.ProviderAdapter; +import org.sonar.api.batch.bootstrap.ProjectReactor; + +public class MutableProjectReactorProvider extends ProviderAdapter { + private ProjectReactor reactor = null; + + public ProjectReactor provide(ProjectReactorBuilder builder) { + if (reactor == null) { + reactor = builder.execute(); + } + return reactor; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ProjectBuildersExecutor.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ProjectBuildersExecutor.java new file mode 100644 index 00000000000..505a80bc117 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ProjectBuildersExecutor.java @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan; + +import org.sonar.api.batch.bootstrap.ProjectBuilder; +import org.sonar.api.batch.bootstrap.ProjectReactor; +import org.sonar.api.batch.bootstrap.internal.ProjectBuilderContext; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.api.utils.log.Profiler; + +public class ProjectBuildersExecutor { + + private static final Logger LOG = Loggers.get(ProjectBuildersExecutor.class); + + private final ProjectBuilder[] projectBuilders; + + public ProjectBuildersExecutor(ProjectBuilder... projectBuilders) { + this.projectBuilders = projectBuilders; + } + + public ProjectBuildersExecutor() { + this(new ProjectBuilder[0]); + } + + public void execute(ProjectReactor reactor) { + if (projectBuilders.length > 0) { + Profiler profiler = Profiler.create(LOG).startInfo("Execute project builders"); + ProjectBuilderContext context = new ProjectBuilderContext(reactor); + + for (ProjectBuilder projectBuilder : projectBuilders) { + projectBuilder.build(context); + } + profiler.stopInfo(); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ProjectExclusions.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ProjectExclusions.java new file mode 100644 index 00000000000..0bea9a81309 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ProjectExclusions.java @@ -0,0 +1,94 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan; + +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.bootstrap.ProjectReactor; +import org.sonar.api.config.Settings; + +/** + * Exclude the sub-projects as defined by the properties sonar.skippedModules and sonar.includedModules + * + * @since 2.12 + */ +public class ProjectExclusions { + + private static final Logger LOG = LoggerFactory.getLogger(ProjectExclusions.class); + + private Settings settings; + + public ProjectExclusions(Settings settings) { + this.settings = settings; + } + + public void apply(ProjectReactor reactor) { + if (!reactor.getProjects().isEmpty() && StringUtils.isNotBlank(reactor.getProjects().get(0).getKey())) { + LOG.info("Apply project exclusions"); + + if (settings.hasKey(CoreProperties.CORE_INCLUDED_MODULES_PROPERTY)) { + LOG.warn("'sonar.includedModules' property is deprecated since version 4.3 and should not be used anymore."); + } + if (settings.hasKey(CoreProperties.CORE_SKIPPED_MODULES_PROPERTY)) { + LOG.warn("'sonar.skippedModules' property is deprecated since version 4.3 and should not be used anymore."); + } + + for (ProjectDefinition project : reactor.getProjects()) { + if (isExcluded(key(project), project == reactor.getRoot())) { + exclude(project); + } + } + } + } + + private boolean isExcluded(String projectKey, boolean isRoot) { + String[] includedKeys = settings.getStringArray(CoreProperties.CORE_INCLUDED_MODULES_PROPERTY); + boolean excluded = false; + if (!isRoot && includedKeys.length > 0) { + excluded = !ArrayUtils.contains(includedKeys, projectKey); + } + String skippedModulesProperty = CoreProperties.CORE_SKIPPED_MODULES_PROPERTY; + if (!excluded) { + String[] excludedKeys = settings.getStringArray(skippedModulesProperty); + excluded = ArrayUtils.contains(excludedKeys, projectKey); + } + if (excluded && isRoot) { + throw new IllegalArgumentException("The root project can't be excluded. Please check the parameters " + skippedModulesProperty + " and sonar.includedModules."); + } + return excluded; + } + + private void exclude(ProjectDefinition project) { + LOG.info(String.format("Exclude project: %s [%s]", project.getName(), project.getKey())); + project.remove(); + } + + static String key(ProjectDefinition project) { + String key = project.getKey(); + if (key.contains(":")) { + return StringUtils.substringAfter(key, ":"); + } + return key; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ProjectLock.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ProjectLock.java new file mode 100644 index 00000000000..d52f49cfd88 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ProjectLock.java @@ -0,0 +1,70 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan; + +import org.sonar.batch.bootstrap.Slf4jLogger; +import org.sonar.home.cache.DirectoryLock; +import org.picocontainer.Startable; +import org.sonar.api.batch.bootstrap.ProjectReactor; + +import java.io.IOException; +import java.nio.channels.OverlappingFileLockException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class ProjectLock implements Startable { + private final DirectoryLock lock; + + public ProjectLock(ProjectReactor projectReactor) { + Path directory = projectReactor.getRoot().getWorkDir().toPath(); + try { + if (!Files.exists(directory)) { + Files.createDirectories(directory); + } + } catch (IOException e) { + throw new IllegalStateException("Failed to create work directory", e); + } + this.lock = new DirectoryLock(directory.toAbsolutePath(), new Slf4jLogger()); + } + + public void tryLock() { + try { + if (!lock.tryLock()) { + failAlreadyInProgress(null); + } + } catch (OverlappingFileLockException e) { + failAlreadyInProgress(e); + } + } + + private static void failAlreadyInProgress(Exception e) { + throw new IllegalStateException("Another SonarQube analysis is already in progress for this project", e); + } + + @Override + public void stop() { + lock.unlock(); + } + + @Override + public void start() { + // nothing to do + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ProjectReactorBuilder.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ProjectReactorBuilder.java new file mode 100644 index 00000000000..2d7e453cfaf --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ProjectReactorBuilder.java @@ -0,0 +1,423 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang.ObjectUtils; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.bootstrap.ProjectReactor; +import org.sonar.api.utils.MessageException; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.api.utils.log.Profiler; +import org.sonar.batch.analysis.AnalysisProperties; +import org.sonar.batch.bootstrap.DroppedPropertyChecker; +import org.sonar.batch.util.BatchUtils; + +/** + * Class that creates a project definition based on a set of properties. + */ +public class ProjectReactorBuilder { + + private static final String INVALID_VALUE_OF_X_FOR_Y = "Invalid value of {0} for {1}"; + + /** + * A map of dropped properties as key and specific message to display for that property + * (what will happen, what should the user do, ...) as a value + */ + private static final Map<String, String> DROPPED_PROPERTIES = ImmutableMap.of( + "sonar.qualitygate", "It will be ignored."); + + private static final Logger LOG = Loggers.get(ProjectReactorBuilder.class); + + /** + * @since 4.1 but not yet exposed in {@link CoreProperties} + */ + private static final String MODULE_KEY_PROPERTY = "sonar.moduleKey"; + + protected static final String PROPERTY_PROJECT_BASEDIR = "sonar.projectBaseDir"; + private static final String PROPERTY_PROJECT_BUILDDIR = "sonar.projectBuildDir"; + private static final String PROPERTY_MODULES = "sonar.modules"; + + /** + * New properties, to be consistent with Sonar naming conventions + * + * @since 1.5 + */ + private static final String PROPERTY_SOURCES = "sonar.sources"; + private static final String PROPERTY_TESTS = "sonar.tests"; + + /** + * Array of all mandatory properties required for a project without child. + */ + private static final String[] MANDATORY_PROPERTIES_FOR_SIMPLE_PROJECT = { + PROPERTY_PROJECT_BASEDIR, CoreProperties.PROJECT_KEY_PROPERTY, CoreProperties.PROJECT_NAME_PROPERTY, + CoreProperties.PROJECT_VERSION_PROPERTY, PROPERTY_SOURCES + }; + + /** + * Array of all mandatory properties required for a project with children. + */ + private static final String[] MANDATORY_PROPERTIES_FOR_MULTIMODULE_PROJECT = {PROPERTY_PROJECT_BASEDIR, CoreProperties.PROJECT_KEY_PROPERTY, + CoreProperties.PROJECT_NAME_PROPERTY, CoreProperties.PROJECT_VERSION_PROPERTY}; + + /** + * Array of all mandatory properties required for a child project before its properties get merged with its parent ones. + */ + private static final String[] MANDATORY_PROPERTIES_FOR_CHILD = {MODULE_KEY_PROPERTY, CoreProperties.PROJECT_NAME_PROPERTY}; + + /** + * Properties that must not be passed from the parent project to its children. + */ + private static final List<String> NON_HERITED_PROPERTIES_FOR_CHILD = Lists.newArrayList(PROPERTY_PROJECT_BASEDIR, CoreProperties.WORKING_DIRECTORY, PROPERTY_MODULES, + CoreProperties.PROJECT_DESCRIPTION_PROPERTY); + + private static final String NON_ASSOCIATED_PROJECT_KEY = "project"; + + private final AnalysisProperties analysisProps; + private final AnalysisMode analysisMode; + private File rootProjectWorkDir; + + public ProjectReactorBuilder(AnalysisProperties props, AnalysisMode analysisMode) { + this.analysisProps = props; + this.analysisMode = analysisMode; + } + + public ProjectReactor execute() { + Profiler profiler = Profiler.create(LOG).startInfo("Process project properties"); + new DroppedPropertyChecker(analysisProps.properties(), DROPPED_PROPERTIES).checkDroppedProperties(); + Map<String, Map<String, String>> propertiesByModuleIdPath = new HashMap<>(); + extractPropertiesByModule(propertiesByModuleIdPath, "", "", analysisProps.properties()); + ProjectDefinition rootProject = defineRootProject(propertiesByModuleIdPath.get(""), null); + rootProjectWorkDir = rootProject.getWorkDir(); + defineChildren(rootProject, propertiesByModuleIdPath, ""); + cleanAndCheckProjectDefinitions(rootProject); + // Since task properties are now empty we should add root module properties + analysisProps.properties().putAll(propertiesByModuleIdPath.get("")); + profiler.stopDebug(); + return new ProjectReactor(rootProject); + } + + private static void extractPropertiesByModule(Map<String, Map<String, String>> propertiesByModuleIdPath, String currentModuleId, String currentModuleIdPath, + Map<String, String> parentProperties) { + if (propertiesByModuleIdPath.containsKey(currentModuleIdPath)) { + throw MessageException.of(String.format("Two modules have the same id: '%s'. Each module must have a unique id.", currentModuleId)); + } + + Map<String, String> currentModuleProperties = new HashMap<>(); + String prefix = !currentModuleId.isEmpty() ? (currentModuleId + ".") : ""; + int prefixLength = prefix.length(); + + // By default all properties starting with module prefix belong to current module + Iterator<Entry<String, String>> it = parentProperties.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<String, String> e = it.next(); + String key = e.getKey(); + if (key.startsWith(prefix)) { + currentModuleProperties.put(key.substring(prefixLength), e.getValue()); + it.remove(); + } + } + String[] moduleIds = getListFromProperty(currentModuleProperties, PROPERTY_MODULES); + // Sort module by reverse lexicographic order to avoid issue when one module id is a prefix of another one + Arrays.sort(moduleIds); + ArrayUtils.reverse(moduleIds); + + propertiesByModuleIdPath.put(currentModuleIdPath, currentModuleProperties); + + for (String moduleId : moduleIds) { + String subModuleIdPath = currentModuleIdPath.isEmpty() ? moduleId : (currentModuleIdPath + "." + moduleId); + extractPropertiesByModule(propertiesByModuleIdPath, moduleId, subModuleIdPath, currentModuleProperties); + } + } + + private static void prepareNonAssociatedProject(Map<String, String> props, AnalysisMode mode) { + if (mode.isIssues() && !props.containsKey(CoreProperties.PROJECT_KEY_PROPERTY)) { + props.put(CoreProperties.PROJECT_KEY_PROPERTY, NON_ASSOCIATED_PROJECT_KEY); + } + } + + protected ProjectDefinition defineRootProject(Map<String, String> rootProperties, @Nullable ProjectDefinition parent) { + prepareNonAssociatedProject(rootProperties, analysisMode); + + if (rootProperties.containsKey(PROPERTY_MODULES)) { + checkMandatoryProperties(rootProperties, MANDATORY_PROPERTIES_FOR_MULTIMODULE_PROJECT); + } else { + checkMandatoryProperties(rootProperties, MANDATORY_PROPERTIES_FOR_SIMPLE_PROJECT); + } + File baseDir = new File(rootProperties.get(PROPERTY_PROJECT_BASEDIR)); + final String projectKey = rootProperties.get(CoreProperties.PROJECT_KEY_PROPERTY); + File workDir; + if (parent == null) { + validateDirectories(rootProperties, baseDir, projectKey); + workDir = initRootProjectWorkDir(baseDir, rootProperties); + } else { + workDir = initModuleWorkDir(baseDir, rootProperties); + } + + return ProjectDefinition.create().setProperties(rootProperties) + .setBaseDir(baseDir) + .setWorkDir(workDir) + .setBuildDir(initModuleBuildDir(baseDir, rootProperties)); + } + + @VisibleForTesting + protected File initRootProjectWorkDir(File baseDir, Map<String, String> rootProperties) { + String workDir = rootProperties.get(CoreProperties.WORKING_DIRECTORY); + if (StringUtils.isBlank(workDir)) { + return new File(baseDir, CoreProperties.WORKING_DIRECTORY_DEFAULT_VALUE); + } + + File customWorkDir = new File(workDir); + if (customWorkDir.isAbsolute()) { + return customWorkDir; + } + return new File(baseDir, customWorkDir.getPath()); + } + + @VisibleForTesting + protected File initModuleWorkDir(File moduleBaseDir, Map<String, String> moduleProperties) { + String workDir = moduleProperties.get(CoreProperties.WORKING_DIRECTORY); + if (StringUtils.isBlank(workDir)) { + return new File(rootProjectWorkDir, BatchUtils.cleanKeyForFilename(moduleProperties.get(CoreProperties.PROJECT_KEY_PROPERTY))); + } + + File customWorkDir = new File(workDir); + if (customWorkDir.isAbsolute()) { + return customWorkDir; + } + return new File(moduleBaseDir, customWorkDir.getPath()); + } + + @CheckForNull + private static File initModuleBuildDir(File moduleBaseDir, Map<String, String> moduleProperties) { + String buildDir = moduleProperties.get(PROPERTY_PROJECT_BUILDDIR); + if (StringUtils.isBlank(buildDir)) { + return null; + } + + File customBuildDir = new File(buildDir); + if (customBuildDir.isAbsolute()) { + return customBuildDir; + } + return new File(moduleBaseDir, customBuildDir.getPath()); + } + + private void defineChildren(ProjectDefinition parentProject, Map<String, Map<String, String>> propertiesByModuleIdPath, String parentModuleIdPath) { + Map<String, String> parentProps = parentProject.properties(); + if (parentProps.containsKey(PROPERTY_MODULES)) { + for (String moduleId : getListFromProperty(parentProps, PROPERTY_MODULES)) { + String moduleIdPath = parentModuleIdPath.isEmpty() ? moduleId : (parentModuleIdPath + "." + moduleId); + Map<String, String> moduleProps = propertiesByModuleIdPath.get(moduleIdPath); + ProjectDefinition childProject = loadChildProject(parentProject, moduleProps, moduleId); + // check the uniqueness of the child key + checkUniquenessOfChildKey(childProject, parentProject); + // the child project may have children as well + defineChildren(childProject, propertiesByModuleIdPath, moduleIdPath); + // and finally add this child project to its parent + parentProject.addSubProject(childProject); + } + } + } + + protected ProjectDefinition loadChildProject(ProjectDefinition parentProject, Map<String, String> moduleProps, String moduleId) { + final File baseDir; + if (moduleProps.containsKey(PROPERTY_PROJECT_BASEDIR)) { + baseDir = resolvePath(parentProject.getBaseDir(), moduleProps.get(PROPERTY_PROJECT_BASEDIR)); + setProjectBaseDir(baseDir, moduleProps, moduleId); + } else { + baseDir = new File(parentProject.getBaseDir(), moduleId); + setProjectBaseDir(baseDir, moduleProps, moduleId); + } + + setModuleKeyAndNameIfNotDefined(moduleProps, moduleId, parentProject.getKey()); + + // and finish + checkMandatoryProperties(moduleProps, MANDATORY_PROPERTIES_FOR_CHILD); + validateDirectories(moduleProps, baseDir, moduleId); + + mergeParentProperties(moduleProps, parentProject.properties()); + + return defineRootProject(moduleProps, parentProject); + } + + @VisibleForTesting + protected static void setModuleKeyAndNameIfNotDefined(Map<String, String> childProps, String moduleId, String parentKey) { + if (!childProps.containsKey(MODULE_KEY_PROPERTY)) { + if (!childProps.containsKey(CoreProperties.PROJECT_KEY_PROPERTY)) { + childProps.put(MODULE_KEY_PROPERTY, parentKey + ":" + moduleId); + } else { + String childKey = childProps.get(CoreProperties.PROJECT_KEY_PROPERTY); + childProps.put(MODULE_KEY_PROPERTY, parentKey + ":" + childKey); + } + } + if (!childProps.containsKey(CoreProperties.PROJECT_NAME_PROPERTY)) { + childProps.put(CoreProperties.PROJECT_NAME_PROPERTY, moduleId); + } + // For backward compatibility with ProjectDefinition + childProps.put(CoreProperties.PROJECT_KEY_PROPERTY, childProps.get(MODULE_KEY_PROPERTY)); + } + + @VisibleForTesting + protected static void checkUniquenessOfChildKey(ProjectDefinition childProject, ProjectDefinition parentProject) { + for (ProjectDefinition definition : parentProject.getSubProjects()) { + if (definition.getKey().equals(childProject.getKey())) { + throw MessageException.of("Project '" + parentProject.getKey() + "' can't have 2 modules with the following key: " + childProject.getKey()); + } + } + } + + protected static void setProjectBaseDir(File baseDir, Map<String, String> childProps, String moduleId) { + if (!baseDir.isDirectory()) { + throw MessageException.of("The base directory of the module '" + moduleId + "' does not exist: " + baseDir.getAbsolutePath()); + } + childProps.put(PROPERTY_PROJECT_BASEDIR, baseDir.getAbsolutePath()); + } + + @VisibleForTesting + protected static void checkMandatoryProperties(Map<String, String> props, String[] mandatoryProps) { + StringBuilder missing = new StringBuilder(); + for (String mandatoryProperty : mandatoryProps) { + if (!props.containsKey(mandatoryProperty)) { + if (missing.length() > 0) { + missing.append(", "); + } + missing.append(mandatoryProperty); + } + } + String moduleKey = StringUtils.defaultIfBlank(props.get(MODULE_KEY_PROPERTY), props.get(CoreProperties.PROJECT_KEY_PROPERTY)); + if (missing.length() != 0) { + throw MessageException.of("You must define the following mandatory properties for '" + (moduleKey == null ? "Unknown" : moduleKey) + "': " + missing); + } + } + + protected static void validateDirectories(Map<String, String> props, File baseDir, String projectId) { + if (!props.containsKey(PROPERTY_MODULES)) { + // SONARPLUGINS-2285 Not an aggregator project so we can validate that paths are correct if defined + + // Check sonar.tests + String[] testPaths = getListFromProperty(props, PROPERTY_TESTS); + checkExistenceOfPaths(projectId, baseDir, testPaths, PROPERTY_TESTS); + } + } + + @VisibleForTesting + protected static void cleanAndCheckProjectDefinitions(ProjectDefinition project) { + if (project.getSubProjects().isEmpty()) { + cleanAndCheckModuleProperties(project); + } else { + cleanAndCheckAggregatorProjectProperties(project); + + // clean modules properties as well + for (ProjectDefinition module : project.getSubProjects()) { + cleanAndCheckProjectDefinitions(module); + } + } + } + + @VisibleForTesting + protected static void cleanAndCheckModuleProperties(ProjectDefinition project) { + Map<String, String> properties = project.properties(); + + // We need to check the existence of source directories + String[] sourcePaths = getListFromProperty(properties, PROPERTY_SOURCES); + checkExistenceOfPaths(project.getKey(), project.getBaseDir(), sourcePaths, PROPERTY_SOURCES); + } + + @VisibleForTesting + protected static void cleanAndCheckAggregatorProjectProperties(ProjectDefinition project) { + Map<String, String> properties = project.properties(); + + // SONARPLUGINS-2295 + String[] sourceDirs = getListFromProperty(properties, PROPERTY_SOURCES); + for (String path : sourceDirs) { + File sourceFolder = resolvePath(project.getBaseDir(), path); + if (sourceFolder.isDirectory()) { + LOG.warn("/!\\ A multi-module project can't have source folders, so '{}' won't be used for the analysis. " + + "If you want to analyse files of this folder, you should create another sub-module and move them inside it.", + sourceFolder.toString()); + } + } + + // "aggregator" project must not have the following properties: + properties.remove(PROPERTY_SOURCES); + properties.remove(PROPERTY_TESTS); + } + + @VisibleForTesting + protected static void mergeParentProperties(Map<String, String> childProps, Map<String, String> parentProps) { + for (Map.Entry<String, String> entry : parentProps.entrySet()) { + String key = entry.getKey(); + if ((!childProps.containsKey(key) || childProps.get(key).equals(entry.getValue())) + && !NON_HERITED_PROPERTIES_FOR_CHILD.contains(key)) { + childProps.put(entry.getKey(), entry.getValue()); + } + } + } + + @VisibleForTesting + protected static void checkExistenceOfPaths(String moduleRef, File baseDir, String[] paths, String propName) { + for (String path : paths) { + File sourceFolder = resolvePath(baseDir, path); + if (!sourceFolder.exists()) { + LOG.error(MessageFormat.format(INVALID_VALUE_OF_X_FOR_Y, propName, moduleRef)); + throw MessageException.of("The folder '" + path + "' does not exist for '" + moduleRef + + "' (base directory = " + baseDir.getAbsolutePath() + ")"); + } + } + } + + protected static File resolvePath(File baseDir, String path) { + Path filePath = Paths.get(path); + if (!filePath.isAbsolute()) { + filePath = baseDir.toPath().resolve(path); + } + return filePath.normalize().toFile(); + } + + /** + * Transforms a comma-separated list String property in to a array of trimmed strings. + * + * This works even if they are separated by whitespace characters (space char, EOL, ...) + * + */ + static String[] getListFromProperty(Map<String, String> properties, String key) { + return (String[]) ObjectUtils.defaultIfNull(StringUtils.stripAll(StringUtils.split(properties.get(key), ',')), new String[0]); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ProjectReactorValidator.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ProjectReactorValidator.java new file mode 100644 index 00000000000..7e36ab54958 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ProjectReactorValidator.java @@ -0,0 +1,101 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan; + +import org.sonar.api.utils.MessageException; + +import org.sonar.batch.analysis.DefaultAnalysisMode; +import com.google.common.base.Joiner; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.Nullable; + +import org.apache.commons.lang.StringUtils; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.bootstrap.ProjectReactor; +import org.sonar.api.config.Settings; +import org.sonar.core.component.ComponentKeys; + +/** + * This class aims at validating project reactor + * @since 3.6 + */ +public class ProjectReactorValidator { + + private static final String SONAR_PHASE = "sonar.phase"; + private final Settings settings; + private final DefaultAnalysisMode mode; + + public ProjectReactorValidator(Settings settings, DefaultAnalysisMode mode) { + this.settings = settings; + this.mode = mode; + } + + public void validate(ProjectReactor reactor) { + String branch = reactor.getRoot().getBranch(); + + List<String> validationMessages = new ArrayList<>(); + checkDeprecatedProperties(validationMessages); + + for (ProjectDefinition moduleDef : reactor.getProjects()) { + if (mode.isIssues()) { + validateModuleIssuesMode(moduleDef, validationMessages); + } else { + validateModule(moduleDef, validationMessages); + } + } + + validateBranch(validationMessages, branch); + + if (!validationMessages.isEmpty()) { + throw MessageException.of("Validation of project reactor failed:\n o " + Joiner.on("\n o ").join(validationMessages)); + } + } + + private static void validateModuleIssuesMode(ProjectDefinition moduleDef, List<String> validationMessages) { + if (!ComponentKeys.isValidModuleKeyIssuesMode(moduleDef.getKey())) { + validationMessages.add(String.format("\"%s\" is not a valid project or module key. " + + "Allowed characters in issues mode are alphanumeric, '-', '_', '.', '/' and ':', with at least one non-digit.", moduleDef.getKey())); + } + } + + private static void validateModule(ProjectDefinition moduleDef, List<String> validationMessages) { + if (!ComponentKeys.isValidModuleKey(moduleDef.getKey())) { + validationMessages.add(String.format("\"%s\" is not a valid project or module key. " + + "Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.", moduleDef.getKey())); + } + } + + private void checkDeprecatedProperties(List<String> validationMessages) { + if (settings.getString(SONAR_PHASE) != null) { + validationMessages.add(String.format("Property \"%s\" is deprecated. Please remove it from your configuration.", SONAR_PHASE)); + } + } + + private static void validateBranch(List<String> validationMessages, @Nullable String branch) { + if (StringUtils.isNotEmpty(branch) && !ComponentKeys.isValidBranch(branch)) { + validationMessages.add(String.format("\"%s\" is not a valid branch name. " + + "Allowed characters are alphanumeric, '-', '_', '.' and '/'.", branch)); + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java new file mode 100644 index 00000000000..ff8108e4871 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java @@ -0,0 +1,277 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan; + +import com.google.common.annotations.VisibleForTesting; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.InstantiationStrategy; +import org.sonar.api.config.Settings; +import org.sonar.api.resources.Languages; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.ResourceTypes; +import org.sonar.api.scan.filesystem.PathResolver; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.batch.DefaultFileLinesContextFactory; +import org.sonar.batch.DefaultProjectTree; +import org.sonar.batch.ProjectConfigurator; +import org.sonar.batch.analysis.AnalysisProperties; +import org.sonar.batch.analysis.AnalysisTempFolderProvider; +import org.sonar.batch.analysis.AnalysisWSLoaderProvider; +import org.sonar.batch.analysis.DefaultAnalysisMode; +import org.sonar.batch.bootstrap.ExtensionInstaller; +import org.sonar.batch.bootstrap.ExtensionMatcher; +import org.sonar.batch.bootstrap.ExtensionUtils; +import org.sonar.batch.bootstrap.MetricProvider; +import org.sonar.batch.cache.ProjectPersistentCacheProvider; +import org.sonar.batch.cpd.CpdExecutor; +import org.sonar.batch.cpd.index.SonarCpdBlockIndex; +import org.sonar.batch.events.EventBus; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.index.Caches; +import org.sonar.batch.index.DefaultIndex; +import org.sonar.batch.issue.DefaultIssueCallback; +import org.sonar.batch.issue.DefaultProjectIssues; +import org.sonar.batch.issue.IssueCache; +import org.sonar.batch.issue.tracking.DefaultServerLineHashesLoader; +import org.sonar.batch.issue.tracking.IssueTransition; +import org.sonar.batch.issue.tracking.LocalIssueTracking; +import org.sonar.batch.issue.tracking.ServerIssueRepository; +import org.sonar.batch.issue.tracking.ServerLineHashesLoader; +import org.sonar.batch.mediumtest.ScanTaskObservers; +import org.sonar.batch.phases.PhasesTimeProfiler; +import org.sonar.batch.profiling.PhasesSumUpTimeProfiler; +import org.sonar.batch.report.ActiveRulesPublisher; +import org.sonar.batch.report.AnalysisContextReportPublisher; +import org.sonar.batch.report.ComponentsPublisher; +import org.sonar.batch.report.CoveragePublisher; +import org.sonar.batch.report.MeasuresPublisher; +import org.sonar.batch.report.MetadataPublisher; +import org.sonar.batch.report.ReportPublisher; +import org.sonar.batch.report.SourcePublisher; +import org.sonar.batch.report.TestExecutionAndCoveragePublisher; +import org.sonar.batch.repository.DefaultProjectRepositoriesLoader; +import org.sonar.batch.repository.DefaultQualityProfileLoader; +import org.sonar.batch.repository.DefaultServerIssuesLoader; +import org.sonar.batch.repository.ProjectRepositories; +import org.sonar.batch.repository.ProjectRepositoriesLoader; +import org.sonar.batch.repository.ProjectRepositoriesProvider; +import org.sonar.batch.repository.QualityProfileLoader; +import org.sonar.batch.repository.QualityProfileProvider; +import org.sonar.batch.repository.ServerIssuesLoader; +import org.sonar.batch.repository.language.DefaultLanguagesRepository; +import org.sonar.batch.repository.user.UserRepositoryLoader; +import org.sonar.batch.rule.ActiveRulesLoader; +import org.sonar.batch.rule.ActiveRulesProvider; +import org.sonar.batch.rule.DefaultActiveRulesLoader; +import org.sonar.batch.rule.DefaultRulesLoader; +import org.sonar.batch.rule.RulesLoader; +import org.sonar.batch.rule.RulesProvider; +import org.sonar.batch.scan.filesystem.InputPathCache; +import org.sonar.batch.scan.measure.DefaultMetricFinder; +import org.sonar.batch.scan.measure.DeprecatedMetricFinder; +import org.sonar.batch.scan.measure.MeasureCache; +import org.sonar.batch.source.CodeColorizers; +import org.sonar.batch.test.TestPlanBuilder; +import org.sonar.batch.test.TestableBuilder; +import org.sonar.core.metric.BatchMetrics; +import org.sonar.core.platform.ComponentContainer; + +public class ProjectScanContainer extends ComponentContainer { + + private static final Logger LOG = Loggers.get(ProjectScanContainer.class); + + private final AnalysisProperties props; + private ProjectLock lock; + + public ProjectScanContainer(ComponentContainer globalContainer, AnalysisProperties props) { + super(globalContainer); + this.props = props; + } + + @Override + protected void doBeforeStart() { + addBatchComponents(); + lock = getComponentByType(ProjectLock.class); + lock.tryLock(); + getComponentByType(WorkDirectoryCleaner.class).execute(); + addBatchExtensions(); + Settings settings = getComponentByType(Settings.class); + if (settings != null && settings.getBoolean(CoreProperties.PROFILING_LOG_PROPERTY)) { + add(PhasesSumUpTimeProfiler.class); + } + if (isTherePreviousAnalysis()) { + addIssueTrackingComponents(); + } + } + + @Override + public ComponentContainer startComponents() { + try { + return super.startComponents(); + } catch (Exception e) { + // ensure that lock is released + if (lock != null) { + lock.stop(); + } + throw e; + } + } + + private void addBatchComponents() { + add( + props, + DefaultAnalysisMode.class, + ProjectReactorBuilder.class, + WorkDirectoryCleaner.class, + new MutableProjectReactorProvider(), + new ImmutableProjectReactorProvider(), + ProjectBuildersExecutor.class, + ProjectLock.class, + EventBus.class, + PhasesTimeProfiler.class, + ResourceTypes.class, + DefaultProjectTree.class, + ProjectExclusions.class, + ProjectReactorValidator.class, + new AnalysisWSLoaderProvider(), + CodeColorizers.class, + MetricProvider.class, + ProjectConfigurator.class, + DefaultIndex.class, + DefaultFileLinesContextFactory.class, + Caches.class, + BatchComponentCache.class, + DefaultIssueCallback.class, + new RulesProvider(), + new ProjectRepositoriesProvider(), + new ProjectPersistentCacheProvider(), + + // temp + new AnalysisTempFolderProvider(), + + // file system + InputPathCache.class, + PathResolver.class, + + // rules + new ActiveRulesProvider(), + new QualityProfileProvider(), + + // issues + IssueCache.class, + DefaultProjectIssues.class, + IssueTransition.class, + + // metrics + DefaultMetricFinder.class, + DeprecatedMetricFinder.class, + + // tests + TestPlanBuilder.class, + TestableBuilder.class, + + // lang + Languages.class, + DefaultLanguagesRepository.class, + + // Measures + MeasureCache.class, + + ProjectSettings.class, + + // Report + BatchMetrics.class, + ReportPublisher.class, + AnalysisContextReportPublisher.class, + MetadataPublisher.class, + ActiveRulesPublisher.class, + ComponentsPublisher.class, + MeasuresPublisher.class, + CoveragePublisher.class, + SourcePublisher.class, + TestExecutionAndCoveragePublisher.class, + + // Cpd + CpdExecutor.class, + SonarCpdBlockIndex.class, + + ScanTaskObservers.class, + UserRepositoryLoader.class); + + addIfMissing(DefaultRulesLoader.class, RulesLoader.class); + addIfMissing(DefaultActiveRulesLoader.class, ActiveRulesLoader.class); + addIfMissing(DefaultQualityProfileLoader.class, QualityProfileLoader.class); + addIfMissing(DefaultProjectRepositoriesLoader.class, ProjectRepositoriesLoader.class); + } + + private void addIssueTrackingComponents() { + add( + LocalIssueTracking.class, + ServerIssueRepository.class); + addIfMissing(DefaultServerIssuesLoader.class, ServerIssuesLoader.class); + addIfMissing(DefaultServerLineHashesLoader.class, ServerLineHashesLoader.class); + } + + private boolean isTherePreviousAnalysis() { + if (getComponentByType(DefaultAnalysisMode.class).isNotAssociated()) { + return false; + } + + return getComponentByType(ProjectRepositories.class).lastAnalysisDate() != null; + } + + private void addBatchExtensions() { + getComponentByType(ExtensionInstaller.class).install(this, new BatchExtensionFilter()); + } + + @Override + protected void doAfterStart() { + DefaultAnalysisMode analysisMode = getComponentByType(DefaultAnalysisMode.class); + analysisMode.printMode(); + LOG.debug("Start recursive analysis of project modules"); + DefaultProjectTree tree = getComponentByType(DefaultProjectTree.class); + scanRecursively(tree.getRootProject()); + if (analysisMode.isMediumTest()) { + getComponentByType(ScanTaskObservers.class).notifyEndOfScanTask(); + } + } + + private void scanRecursively(Project module) { + for (Project subModules : module.getModules()) { + scanRecursively(subModules); + } + scan(module); + } + + @VisibleForTesting + void scan(Project module) { + new ModuleScanContainer(this, module).execute(); + } + + static class BatchExtensionFilter implements ExtensionMatcher { + @Override + public boolean accept(Object extension) { + return ExtensionUtils.isBatchSide(extension) + && ExtensionUtils.isInstantiationStrategy(extension, InstantiationStrategy.PER_BATCH); + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ProjectSettings.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ProjectSettings.java new file mode 100644 index 00000000000..382d02424a3 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ProjectSettings.java @@ -0,0 +1,62 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan; + +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.bootstrap.ProjectReactor; +import org.sonar.api.config.PropertyDefinitions; +import org.sonar.api.config.Settings; +import org.sonar.api.utils.MessageException; +import org.sonar.batch.analysis.DefaultAnalysisMode; +import org.sonar.batch.bootstrap.GlobalSettings; +import org.sonar.batch.repository.ProjectRepositories; + +public class ProjectSettings extends Settings { + + private final GlobalSettings globalSettings; + private final ProjectRepositories projectRepositories; + private final DefaultAnalysisMode mode; + + public ProjectSettings(ProjectReactor reactor, GlobalSettings globalSettings, PropertyDefinitions propertyDefinitions, + ProjectRepositories projectRepositories, DefaultAnalysisMode mode) { + super(propertyDefinitions); + this.mode = mode; + getEncryption().setPathToSecretKey(globalSettings.getString(CoreProperties.ENCRYPTION_SECRET_KEY_PATH)); + this.globalSettings = globalSettings; + this.projectRepositories = projectRepositories; + init(reactor); + } + + private void init(ProjectReactor reactor) { + addProperties(globalSettings.getProperties()); + + addProperties(projectRepositories.settings(reactor.getRoot().getKeyWithBranch())); + + addProperties(reactor.getRoot().properties()); + } + + @Override + protected void doOnGetProperties(String key) { + if (mode.isIssues() && key.endsWith(".secured") && !key.contains(".license")) { + throw MessageException.of("Access to the secured property '" + key + + "' is not possible in issues mode. The SonarQube plugin which requires this property must be deactivated in issues mode."); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/WorkDirectoryCleaner.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/WorkDirectoryCleaner.java new file mode 100644 index 00000000000..b4857bf6afb --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/WorkDirectoryCleaner.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan; + +import org.sonar.api.batch.bootstrap.ProjectReactor; +import org.sonar.core.util.FileUtils; +import org.sonar.home.cache.DirectoryLock; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Iterator; + +public class WorkDirectoryCleaner { + private Path workDir; + + public WorkDirectoryCleaner(ProjectReactor projectReactor) { + workDir = projectReactor.getRoot().getWorkDir().toPath(); + } + + public void execute() { + if (!Files.exists(workDir)) { + return; + } + + try (DirectoryStream<Path> stream = list()) { + + Iterator<Path> it = stream.iterator(); + while (it.hasNext()) { + FileUtils.deleteQuietly(it.next().toFile()); + } + } catch (IOException e) { + throw new IllegalStateException("Failed to clean working directory: " + workDir.toString(), e); + } + } + + private DirectoryStream<Path> list() throws IOException { + return Files.newDirectoryStream(workDir, new DirectoryStream.Filter<Path>() { + @Override + public boolean accept(Path entry) throws IOException { + return !DirectoryLock.LOCK_FILE_NAME.equals(entry.getFileName().toString()); + } + }); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/AdditionalFilePredicates.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/AdditionalFilePredicates.java new file mode 100644 index 00000000000..b68b6a3625a --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/AdditionalFilePredicates.java @@ -0,0 +1,49 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.AbstractFilePredicate; +import org.sonar.api.batch.fs.internal.DefaultInputFile; + +/** + * Additional {@link org.sonar.api.batch.fs.FilePredicate}s that are + * not published in public API + */ +class AdditionalFilePredicates { + + private AdditionalFilePredicates() { + // only static inner classes + } + + static class KeyPredicate extends AbstractFilePredicate { + private final String key; + + KeyPredicate(String key) { + this.key = key; + } + + @Override + public boolean apply(InputFile f) { + return key.equals(((DefaultInputFile) f).key()); + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/ComponentIndexer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/ComponentIndexer.java new file mode 100644 index 00000000000..761f6d15823 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/ComponentIndexer.java @@ -0,0 +1,68 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.SonarIndex; +import org.sonar.api.batch.fs.InputDir; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.resources.File; +import org.sonar.api.resources.Languages; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.batch.index.BatchComponent; +import org.sonar.batch.index.BatchComponentCache; + +/** + * Index all files/directories of the module in SQ database and importing source code. + * + * @since 4.2 + */ +@BatchSide +public class ComponentIndexer { + + private final Languages languages; + private final SonarIndex sonarIndex; + private final Project module; + private final BatchComponentCache componentCache; + + public ComponentIndexer(Project module, Languages languages, SonarIndex sonarIndex, BatchComponentCache componentCache) { + this.module = module; + this.languages = languages; + this.sonarIndex = sonarIndex; + this.componentCache = componentCache; + } + + public void execute(DefaultModuleFileSystem fs) { + module.setBaseDir(fs.baseDir()); + + for (InputFile inputFile : fs.inputFiles()) { + String languageKey = inputFile.language(); + boolean unitTest = InputFile.Type.TEST == inputFile.type(); + Resource sonarFile = File.create(inputFile.relativePath(), languages.get(languageKey), unitTest); + sonarIndex.index(sonarFile); + BatchComponent file = componentCache.get(sonarFile); + file.setInputComponent(inputFile); + Resource sonarDir = file.parent().resource(); + InputDir inputDir = fs.inputDir(inputFile.file().getParentFile()); + componentCache.get(sonarDir).setInputComponent(inputDir); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystem.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystem.java new file mode 100644 index 00000000000..125a73d0078 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystem.java @@ -0,0 +1,285 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.sonar.api.batch.fs.InputFile.Status; + +import org.sonar.batch.analysis.DefaultAnalysisMode; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +import java.io.File; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + +import org.apache.commons.lang.StringUtils; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.fs.FilePredicate; +import org.sonar.api.batch.fs.internal.DefaultFileSystem; +import org.sonar.api.config.Settings; +import org.sonar.api.resources.Project; +import org.sonar.api.scan.filesystem.FileQuery; +import org.sonar.api.scan.filesystem.ModuleFileSystem; +import org.sonar.api.utils.MessageException; + +/** + * @since 3.5 + */ +public class DefaultModuleFileSystem extends DefaultFileSystem implements ModuleFileSystem { + + private String moduleKey; + private FileIndexer indexer; + private Settings settings; + + private File buildDir; + private List<File> sourceDirsOrFiles = Lists.newArrayList(); + private List<File> testDirsOrFiles = Lists.newArrayList(); + private List<File> binaryDirs = Lists.newArrayList(); + private ComponentIndexer componentIndexer; + private boolean initialized; + + public DefaultModuleFileSystem(ModuleInputFileCache moduleInputFileCache, Project project, + Settings settings, FileIndexer indexer, ModuleFileSystemInitializer initializer, ComponentIndexer componentIndexer, DefaultAnalysisMode mode) { + super(initializer.baseDir(), moduleInputFileCache); + setFields(project, settings, indexer, initializer, componentIndexer, mode); + } + + @VisibleForTesting + public DefaultModuleFileSystem(Project project, + Settings settings, FileIndexer indexer, ModuleFileSystemInitializer initializer, ComponentIndexer componentIndexer, DefaultAnalysisMode mode) { + super(initializer.baseDir().toPath()); + setFields(project, settings, indexer, initializer, componentIndexer, mode); + } + + private void setFields(Project project, + Settings settings, FileIndexer indexer, ModuleFileSystemInitializer initializer, ComponentIndexer componentIndexer, DefaultAnalysisMode mode) { + this.componentIndexer = componentIndexer; + this.moduleKey = project.getKey(); + this.settings = settings; + this.indexer = indexer; + setWorkDir(initializer.workingDir()); + this.buildDir = initializer.buildDir(); + this.sourceDirsOrFiles = initializer.sources(); + this.testDirsOrFiles = initializer.tests(); + this.binaryDirs = initializer.binaryDirs(); + + // filter the files sensors have access to + if (!mode.scanAllFiles()) { + setDefaultPredicate(predicates.not(predicates.hasStatus(Status.SAME))); + } + } + + public boolean isInitialized() { + return initialized; + } + + public String moduleKey() { + return moduleKey; + } + + @Override + @CheckForNull + public File buildDir() { + return buildDir; + } + + @Override + public List<File> sourceDirs() { + return keepOnlyDirs(sourceDirsOrFiles); + } + + public List<File> sources() { + return sourceDirsOrFiles; + } + + @Override + public List<File> testDirs() { + return keepOnlyDirs(testDirsOrFiles); + } + + public List<File> tests() { + return testDirsOrFiles; + } + + private static List<File> keepOnlyDirs(List<File> dirsOrFiles) { + List<File> result = new ArrayList<>(); + for (File f : dirsOrFiles) { + if (f.isDirectory()) { + result.add(f); + } + } + return result; + } + + @Override + public List<File> binaryDirs() { + return binaryDirs; + } + + @Override + public Charset encoding() { + final Charset charset; + String encoding = settings.getString(CoreProperties.ENCODING_PROPERTY); + if (StringUtils.isNotEmpty(encoding)) { + charset = Charset.forName(StringUtils.trim(encoding)); + } else { + charset = Charset.defaultCharset(); + } + return charset; + } + + @Override + public boolean isDefaultJvmEncoding() { + return !settings.hasKey(CoreProperties.ENCODING_PROPERTY); + } + + /** + * Should not be used - only for old plugins + * + * @deprecated since 4.0 + */ + @Deprecated + void addSourceDir(File dir) { + throw modificationNotPermitted(); + } + + /** + * Should not be used - only for old plugins + * + * @deprecated since 4.0 + */ + @Deprecated + void addTestDir(File dir) { + throw modificationNotPermitted(); + } + + private static UnsupportedOperationException modificationNotPermitted() { + return new UnsupportedOperationException("Modifications of the file system are not permitted"); + } + + /** + * @return + * @deprecated in 4.2. Replaced by {@link #encoding()} + */ + @Override + @Deprecated + public Charset sourceCharset() { + return encoding(); + } + + /** + * @deprecated in 4.2. Replaced by {@link #workDir()} + */ + @Deprecated + @Override + public File workingDir() { + return workDir(); + } + + @Override + public List<File> files(FileQuery query) { + doPreloadFiles(); + Collection<FilePredicate> predicates = Lists.newArrayList(); + for (Map.Entry<String, Collection<String>> entry : query.attributes().entrySet()) { + predicates.add(fromDeprecatedAttribute(entry.getKey(), entry.getValue())); + } + return ImmutableList.copyOf(files(predicates().and(predicates))); + } + + @Override + protected void doPreloadFiles() { + if (!initialized) { + throw MessageException.of("Accessing the filesystem before the Sensor phase is not supported. Please update your plugin."); + } + } + + public void index() { + if (initialized) { + throw MessageException.of("Module filesystem can only be indexed once"); + } + initialized = true; + indexer.index(this); + if (componentIndexer != null) { + componentIndexer.execute(this); + } + } + + private FilePredicate fromDeprecatedAttribute(String key, Collection<String> value) { + if ("TYPE".equals(key)) { + return predicates().or(Collections2.transform(value, new Function<String, FilePredicate>() { + @Override + public FilePredicate apply(@Nullable String s) { + return s == null ? predicates().all() : predicates().hasType(org.sonar.api.batch.fs.InputFile.Type.valueOf(s)); + } + })); + } + if ("STATUS".equals(key)) { + return predicates().or(Collections2.transform(value, new Function<String, FilePredicate>() { + @Override + public FilePredicate apply(@Nullable String s) { + return s == null ? predicates().all() : predicates().hasStatus(org.sonar.api.batch.fs.InputFile.Status.valueOf(s)); + } + })); + } + if ("LANG".equals(key)) { + return predicates().or(Collections2.transform(value, new Function<String, FilePredicate>() { + @Override + public FilePredicate apply(@Nullable String s) { + return s == null ? predicates().all() : predicates().hasLanguage(s); + } + })); + } + if ("CMP_KEY".equals(key)) { + return predicates().or(Collections2.transform(value, new Function<String, FilePredicate>() { + @Override + public FilePredicate apply(@Nullable String s) { + return s == null ? predicates().all() : new AdditionalFilePredicates.KeyPredicate(s); + } + })); + } + throw new IllegalArgumentException("Unsupported file attribute: " + key); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DefaultModuleFileSystem that = (DefaultModuleFileSystem) o; + return moduleKey.equals(that.moduleKey); + } + + @Override + public int hashCode() { + return moduleKey.hashCode(); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/DeprecatedFileFilters.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/DeprecatedFileFilters.java new file mode 100644 index 00000000000..29968e7e102 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/DeprecatedFileFilters.java @@ -0,0 +1,80 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.InputFileFilter; +import org.sonar.api.scan.filesystem.FileSystemFilter; +import org.sonar.api.scan.filesystem.FileType; +import org.sonar.api.scan.filesystem.ModuleFileSystem; + +public class DeprecatedFileFilters implements InputFileFilter { + private final FileSystemFilter[] filters; + + public DeprecatedFileFilters(FileSystemFilter[] filters) { + this.filters = filters; + } + + public DeprecatedFileFilters() { + this(new FileSystemFilter[0]); + } + + @Override + public boolean accept(InputFile inputFile) { + if (filters.length > 0) { + DeprecatedContext context = new DeprecatedContext(inputFile); + for (FileSystemFilter filter : filters) { + if (!filter.accept(inputFile.file(), context)) { + return false; + } + } + } + return true; + } + + static class DeprecatedContext implements FileSystemFilter.Context { + private final InputFile inputFile; + + DeprecatedContext(InputFile inputFile) { + this.inputFile = inputFile; + } + + @Override + public ModuleFileSystem fileSystem() { + throw new UnsupportedOperationException("Not supported since 4.0"); + } + + @Override + public FileType type() { + String type = inputFile.type().name(); + return FileType.valueOf(type); + } + + @Override + public String relativePath() { + return inputFile.relativePath(); + } + + @Override + public String canonicalPath() { + return inputFile.absolutePath(); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/ExclusionFilters.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/ExclusionFilters.java new file mode 100644 index 00000000000..2a2952884f5 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/ExclusionFilters.java @@ -0,0 +1,131 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.apache.commons.lang.ArrayUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.PathPattern; +import org.sonar.api.scan.filesystem.FileExclusions; + +@BatchSide +public class ExclusionFilters { + + private static final Logger LOG = LoggerFactory.getLogger(ExclusionFilters.class); + + private final FileExclusions exclusionSettings; + + private PathPattern[] mainInclusions; + private PathPattern[] mainExclusions; + private PathPattern[] testInclusions; + private PathPattern[] testExclusions; + + public ExclusionFilters(FileExclusions exclusions) { + this.exclusionSettings = exclusions; + } + + public void prepare() { + mainInclusions = prepareMainInclusions(); + mainExclusions = prepareMainExclusions(); + testInclusions = prepareTestInclusions(); + testExclusions = prepareTestExclusions(); + log("Included sources: ", mainInclusions); + log("Excluded sources: ", mainExclusions); + log("Included tests: ", testInclusions); + log("Excluded tests: ", testExclusions); + } + + public boolean hasPattern() { + return mainInclusions.length > 0 || mainExclusions.length > 0 || testInclusions.length > 0 || testExclusions.length > 0; + } + + private void log(String title, PathPattern[] patterns) { + if (patterns.length > 0) { + LOG.info(title); + for (PathPattern pattern : patterns) { + LOG.info(" " + pattern); + } + } + } + + public boolean accept(InputFile inputFile, InputFile.Type type) { + PathPattern[] inclusionPatterns; + PathPattern[] exclusionPatterns; + if (InputFile.Type.MAIN == type) { + inclusionPatterns = mainInclusions; + exclusionPatterns = mainExclusions; + } else if (InputFile.Type.TEST == type) { + inclusionPatterns = testInclusions; + exclusionPatterns = testExclusions; + } else { + throw new IllegalArgumentException("Unknown file type: " + type); + } + + if (inclusionPatterns.length > 0) { + boolean matchInclusion = false; + for (PathPattern pattern : inclusionPatterns) { + matchInclusion |= pattern.match(inputFile); + } + if (!matchInclusion) { + return false; + } + } + if (exclusionPatterns.length > 0) { + for (PathPattern pattern : exclusionPatterns) { + if (pattern.match(inputFile)) { + return false; + } + } + } + return true; + } + + PathPattern[] prepareMainInclusions() { + if (exclusionSettings.sourceInclusions().length > 0) { + // User defined params + return PathPattern.create(exclusionSettings.sourceInclusions()); + } + return new PathPattern[0]; + } + + PathPattern[] prepareTestInclusions() { + return PathPattern.create(computeTestInclusions()); + } + + private String[] computeTestInclusions() { + if (exclusionSettings.testInclusions().length > 0) { + // User defined params + return exclusionSettings.testInclusions(); + } + return ArrayUtils.EMPTY_STRING_ARRAY; + } + + PathPattern[] prepareMainExclusions() { + String[] patterns = (String[]) ArrayUtils.addAll( + exclusionSettings.sourceExclusions(), computeTestInclusions()); + return PathPattern.create(patterns); + } + + PathPattern[] prepareTestExclusions() { + return PathPattern.create(exclusionSettings.testExclusions()); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/FileIndexer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/FileIndexer.java new file mode 100644 index 00000000000..9ebf91886a0 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/FileIndexer.java @@ -0,0 +1,268 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.InputFile.Type; +import org.sonar.api.batch.fs.InputFileFilter; +import org.sonar.api.batch.fs.internal.DefaultInputDir; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.scan.filesystem.PathResolver; +import org.sonar.api.utils.MessageException; +import org.sonar.batch.util.ProgressReport; +import org.sonar.home.cache.DirectoryLock; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystemLoopException; +import java.nio.file.FileVisitOption; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * Index input files into {@link InputPathCache}. + */ +@BatchSide +public class FileIndexer { + + private static final Logger LOG = LoggerFactory.getLogger(FileIndexer.class); + private final List<InputFileFilter> filters; + private final boolean isAggregator; + private final ExclusionFilters exclusionFilters; + private final InputFileBuilderFactory inputFileBuilderFactory; + + private ProgressReport progressReport; + private ExecutorService executorService; + private List<Future<Void>> tasks; + + public FileIndexer(List<InputFileFilter> filters, ExclusionFilters exclusionFilters, InputFileBuilderFactory inputFileBuilderFactory, + ProjectDefinition def) { + this.filters = filters; + this.exclusionFilters = exclusionFilters; + this.inputFileBuilderFactory = inputFileBuilderFactory; + this.isAggregator = !def.getSubProjects().isEmpty(); + } + + void index(DefaultModuleFileSystem fileSystem) { + if (isAggregator) { + // No indexing for an aggregator module + return; + } + progressReport = new ProgressReport("Report about progress of file indexation", TimeUnit.SECONDS.toMillis(10)); + progressReport.start("Index files"); + exclusionFilters.prepare(); + + Progress progress = new Progress(); + + InputFileBuilder inputFileBuilder = inputFileBuilderFactory.create(fileSystem); + int threads = Math.max(1, Runtime.getRuntime().availableProcessors() - 1); + executorService = Executors.newFixedThreadPool(threads, new ThreadFactoryBuilder().setNameFormat("FileIndexer-%d").build()); + tasks = new ArrayList<>(); + indexFiles(fileSystem, progress, inputFileBuilder, fileSystem.sources(), InputFile.Type.MAIN); + indexFiles(fileSystem, progress, inputFileBuilder, fileSystem.tests(), InputFile.Type.TEST); + + waitForTasksToComplete(); + + progressReport.stop(progress.count() + " files indexed"); + + if (exclusionFilters.hasPattern()) { + LOG.info(progress.excludedByPatternsCount() + " files ignored because of inclusion/exclusion patterns"); + } + } + + private void waitForTasksToComplete() { + executorService.shutdown(); + for (Future<Void> task : tasks) { + try { + task.get(); + } catch (ExecutionException e) { + // Unwrap ExecutionException + throw e.getCause() instanceof RuntimeException ? (RuntimeException) e.getCause() : new IllegalStateException(e.getCause()); + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + } + } + + private void indexFiles(DefaultModuleFileSystem fileSystem, Progress progress, InputFileBuilder inputFileBuilder, List<File> sources, InputFile.Type type) { + try { + for (File dirOrFile : sources) { + if (dirOrFile.isDirectory()) { + indexDirectory(inputFileBuilder, fileSystem, progress, dirOrFile, type); + } else { + indexFile(inputFileBuilder, fileSystem, progress, dirOrFile.toPath(), type); + } + } + } catch (IOException e) { + throw new IllegalStateException("Failed to index files", e); + } + } + + private void indexDirectory(final InputFileBuilder inputFileBuilder, final DefaultModuleFileSystem fileSystem, final Progress status, + final File dirToIndex, final InputFile.Type type) throws IOException { + Files.walkFileTree(dirToIndex.toPath().normalize(), Collections.singleton(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, + new IndexFileVisitor(inputFileBuilder, fileSystem, status, type)); + } + + private void indexFile(InputFileBuilder inputFileBuilder, DefaultModuleFileSystem fileSystem, Progress progress, Path sourceFile, InputFile.Type type) throws IOException { + // get case of real file without resolving link + Path realFile = sourceFile.toRealPath(LinkOption.NOFOLLOW_LINKS); + DefaultInputFile inputFile = inputFileBuilder.create(realFile.toFile()); + if (inputFile != null) { + // Set basedir on input file prior to adding it to the FS since exclusions filters may require the absolute path + inputFile.setModuleBaseDir(fileSystem.baseDirPath()); + if (exclusionFilters.accept(inputFile, type)) { + indexFile(inputFileBuilder, fileSystem, progress, inputFile, type); + } else { + progress.increaseExcludedByPatternsCount(); + } + } + } + + private void indexFile(final InputFileBuilder inputFileBuilder, final DefaultModuleFileSystem fs, + final Progress status, final DefaultInputFile inputFile, final InputFile.Type type) { + + tasks.add(executorService.submit(new Callable<Void>() { + @Override + public Void call() { + DefaultInputFile completedInputFile = inputFileBuilder.completeAndComputeMetadata(inputFile, type); + if (completedInputFile != null && accept(completedInputFile)) { + fs.add(completedInputFile); + status.markAsIndexed(completedInputFile); + File parentDir = completedInputFile.file().getParentFile(); + String relativePath = new PathResolver().relativePath(fs.baseDir(), parentDir); + if (relativePath != null) { + DefaultInputDir inputDir = new DefaultInputDir(fs.moduleKey(), relativePath); + fs.add(inputDir); + } + } + return null; + } + })); + + } + + private boolean accept(InputFile inputFile) { + // InputFileFilter extensions + for (InputFileFilter filter : filters) { + if (!filter.accept(inputFile)) { + return false; + } + } + return true; + } + + private class IndexFileVisitor implements FileVisitor<Path> { + private InputFileBuilder inputFileBuilder; + private DefaultModuleFileSystem fileSystem; + private Progress status; + private Type type; + + IndexFileVisitor(InputFileBuilder inputFileBuilder, DefaultModuleFileSystem fileSystem, Progress status, InputFile.Type type) { + this.inputFileBuilder = inputFileBuilder; + this.fileSystem = fileSystem; + this.status = status; + this.type = type; + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + Path fileName = dir.getFileName(); + + if (fileName != null && fileName.toString().length() > 1 && fileName.toString().charAt(0) == '.') { + return FileVisitResult.SKIP_SUBTREE; + } + if (Files.isHidden(dir)) { + return FileVisitResult.SKIP_SUBTREE; + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (!Files.isHidden(file) && !DirectoryLock.LOCK_FILE_NAME.equals(file.getFileName().toString())) { + indexFile(inputFileBuilder, fileSystem, status, file, type); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { + if (exc != null && exc instanceof FileSystemLoopException) { + LOG.warn("Not indexing due to symlink loop: {}", file.toFile()); + return FileVisitResult.CONTINUE; + } + + throw exc; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + return FileVisitResult.CONTINUE; + } + } + + private class Progress { + private final Set<Path> indexed = new HashSet<>(); + private int excludedByPatternsCount = 0; + + synchronized void markAsIndexed(InputFile inputFile) { + if (indexed.contains(inputFile.path())) { + throw MessageException.of("File " + inputFile + " can't be indexed twice. Please check that inclusion/exclusion patterns produce " + + "disjoint sets for main and test files"); + } + indexed.add(inputFile.path()); + progressReport.message(indexed.size() + " files indexed... (last one was " + inputFile.relativePath() + ")"); + } + + void increaseExcludedByPatternsCount() { + excludedByPatternsCount++; + } + + public int excludedByPatternsCount() { + return excludedByPatternsCount; + } + + int count() { + return indexed.size(); + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/FileSystemLogger.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/FileSystemLogger.java new file mode 100644 index 00000000000..0d717301283 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/FileSystemLogger.java @@ -0,0 +1,92 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import com.google.common.annotations.VisibleForTesting; +import java.io.File; +import java.nio.charset.Charset; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.scan.filesystem.PathResolver; + +@BatchSide +public class FileSystemLogger { + + private final DefaultModuleFileSystem fs; + + public FileSystemLogger(DefaultModuleFileSystem fs) { + this.fs = fs; + } + + public void log() { + doLog(LoggerFactory.getLogger(getClass())); + } + + @VisibleForTesting + void doLog(Logger logger) { + logDir(logger, "Base dir: ", fs.baseDir()); + logDir(logger, "Working dir: ", fs.workDir()); + logPaths(logger, "Source paths: ", fs.baseDir(), fs.sources()); + logPaths(logger, "Test paths: ", fs.baseDir(), fs.tests()); + logPaths(logger, "Binary dirs: ", fs.baseDir(), fs.binaryDirs()); + logEncoding(logger, fs.encoding()); + } + + private void logEncoding(Logger logger, Charset charset) { + if (!fs.isDefaultJvmEncoding()) { + logger.info("Source encoding: " + charset.displayName() + ", default locale: " + Locale.getDefault()); + } else { + logger.warn("Source encoding is platform dependent (" + charset.displayName() + "), default locale: " + Locale.getDefault()); + } + } + + private static void logPaths(Logger logger, String label, File baseDir, List<File> paths) { + if (!paths.isEmpty()) { + PathResolver resolver = new PathResolver(); + StringBuilder sb = new StringBuilder(label); + for (Iterator<File> it = paths.iterator(); it.hasNext();) { + File file = it.next(); + String relativePathToBaseDir = resolver.relativePath(baseDir, file); + if (relativePathToBaseDir == null) { + sb.append(file); + } else if (StringUtils.isBlank(relativePathToBaseDir)) { + sb.append("."); + } else { + sb.append(relativePathToBaseDir); + } + if (it.hasNext()) { + sb.append(", "); + } + } + logger.info(sb.toString()); + } + } + + private void logDir(Logger logger, String label, File dir) { + if (dir != null) { + logger.info(label + dir.getAbsolutePath()); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/InputFileBuilder.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/InputFileBuilder.java new file mode 100644 index 00000000000..49cbd423cf9 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/InputFileBuilder.java @@ -0,0 +1,111 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.fs.internal.FileMetadata; +import org.sonar.api.config.Settings; +import org.sonar.api.scan.filesystem.PathResolver; + +import javax.annotation.CheckForNull; + +import java.io.File; + +class InputFileBuilder { + + private static final Logger LOG = LoggerFactory.getLogger(InputFileBuilder.class); + + private final String moduleKey; + private final PathResolver pathResolver; + private final LanguageDetection langDetection; + private final StatusDetection statusDetection; + private final DefaultModuleFileSystem fs; + private final Settings settings; + private final FileMetadata fileMetadata; + + InputFileBuilder(String moduleKey, PathResolver pathResolver, LanguageDetection langDetection, + StatusDetection statusDetection, DefaultModuleFileSystem fs, Settings settings, FileMetadata fileMetadata) { + this.moduleKey = moduleKey; + this.pathResolver = pathResolver; + this.langDetection = langDetection; + this.statusDetection = statusDetection; + this.fs = fs; + this.settings = settings; + this.fileMetadata = fileMetadata; + } + + String moduleKey() { + return moduleKey; + } + + PathResolver pathResolver() { + return pathResolver; + } + + LanguageDetection langDetection() { + return langDetection; + } + + StatusDetection statusDetection() { + return statusDetection; + } + + FileSystem fs() { + return fs; + } + + @CheckForNull + DefaultInputFile create(File file) { + String relativePath = pathResolver.relativePath(fs.baseDir(), file); + if (relativePath == null) { + LOG.warn("File '{}' is ignored. It is not located in module basedir '{}'.", file.getAbsolutePath(), fs.baseDir()); + return null; + } + return new DefaultInputFile(moduleKey, relativePath); + } + + /** + * Optimization to not compute InputFile metadata if the file is excluded from analysis. + */ + @CheckForNull + DefaultInputFile completeAndComputeMetadata(DefaultInputFile inputFile, InputFile.Type type) { + inputFile.setType(type); + inputFile.setModuleBaseDir(fs.baseDir().toPath()); + inputFile.setCharset(fs.encoding()); + + String lang = langDetection.language(inputFile); + if (lang == null && !settings.getBoolean(CoreProperties.IMPORT_UNKNOWN_FILES_KEY)) { + return null; + } + inputFile.setLanguage(lang); + + inputFile.initMetadata(fileMetadata.readMetadata(inputFile.file(), fs.encoding())); + + inputFile.setStatus(statusDetection.status(inputFile.moduleKey(), inputFile.relativePath(), inputFile.hash())); + + return inputFile; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/InputFileBuilderFactory.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/InputFileBuilderFactory.java new file mode 100644 index 00000000000..46dc2b2fbcd --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/InputFileBuilderFactory.java @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.fs.internal.FileMetadata; +import org.sonar.api.config.Settings; +import org.sonar.api.scan.filesystem.PathResolver; + +@BatchSide +public class InputFileBuilderFactory { + + private final String moduleKey; + private final PathResolver pathResolver; + private final LanguageDetectionFactory langDetectionFactory; + private final StatusDetectionFactory statusDetectionFactory; + private final Settings settings; + private final FileMetadata fileMetadata; + + public InputFileBuilderFactory(ProjectDefinition def, PathResolver pathResolver, LanguageDetectionFactory langDetectionFactory, + StatusDetectionFactory statusDetectionFactory, Settings settings, FileMetadata fileMetadata) { + this.fileMetadata = fileMetadata; + this.moduleKey = def.getKeyWithBranch(); + this.pathResolver = pathResolver; + this.langDetectionFactory = langDetectionFactory; + this.statusDetectionFactory = statusDetectionFactory; + this.settings = settings; + } + + InputFileBuilder create(DefaultModuleFileSystem fs) { + return new InputFileBuilder(moduleKey, pathResolver, langDetectionFactory.create(), statusDetectionFactory.create(), fs, settings, fileMetadata); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/InputPathCache.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/InputPathCache.java new file mode 100644 index 00000000000..3375f92c7ff --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/InputPathCache.java @@ -0,0 +1,92 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import com.google.common.collect.Table; +import com.google.common.collect.TreeBasedTable; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.fs.InputDir; +import org.sonar.api.batch.fs.InputFile; + +import javax.annotation.CheckForNull; + +/** + * Cache of all files and dirs. This cache is shared amongst all project modules. Inclusion and + * exclusion patterns are already applied. + */ +@BatchSide +public class InputPathCache { + + private final Table<String, String, InputFile> inputFileCache = TreeBasedTable.create(); + private final Table<String, String, InputDir> inputDirCache = TreeBasedTable.create(); + + public Iterable<InputFile> allFiles() { + return inputFileCache.values(); + } + + public Iterable<InputDir> allDirs() { + return inputDirCache.values(); + } + + public Iterable<InputFile> filesByModule(String moduleKey) { + return inputFileCache.row(moduleKey).values(); + } + + public Iterable<InputDir> dirsByModule(String moduleKey) { + return inputDirCache.row(moduleKey).values(); + } + + public InputPathCache removeModule(String moduleKey) { + inputFileCache.row(moduleKey).clear(); + inputDirCache.row(moduleKey).clear(); + return this; + } + + public InputPathCache remove(String moduleKey, InputFile inputFile) { + inputFileCache.remove(moduleKey, inputFile.relativePath()); + return this; + } + + public InputPathCache remove(String moduleKey, InputDir inputDir) { + inputDirCache.remove(moduleKey, inputDir.relativePath()); + return this; + } + + public InputPathCache put(String moduleKey, InputFile inputFile) { + inputFileCache.put(moduleKey, inputFile.relativePath(), inputFile); + return this; + } + + public InputPathCache put(String moduleKey, InputDir inputDir) { + inputDirCache.put(moduleKey, inputDir.relativePath(), inputDir); + return this; + } + + @CheckForNull + public InputFile getFile(String moduleKey, String relativePath) { + return inputFileCache.get(moduleKey, relativePath); + } + + @CheckForNull + public InputDir getDir(String moduleKey, String relativePath) { + return inputDirCache.get(moduleKey, relativePath); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/LanguageDetection.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/LanguageDetection.java new file mode 100644 index 00000000000..db1300e2a97 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/LanguageDetection.java @@ -0,0 +1,143 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.sonar.batch.repository.language.Language; +import org.sonar.batch.repository.language.LanguagesRepository; + +import com.google.common.base.Joiner; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.PathPattern; +import org.sonar.api.config.Settings; +import org.sonar.api.utils.MessageException; + +import javax.annotation.CheckForNull; + +import java.text.MessageFormat; +import java.util.List; +import java.util.Map; + +/** + * Detect language of a source file based on its suffix and configured patterns. + */ +class LanguageDetection { + + private static final Logger LOG = LoggerFactory.getLogger(LanguageDetection.class); + + /** + * Lower-case extension -> languages + */ + private final Map<String, PathPattern[]> patternsByLanguage = Maps.newLinkedHashMap(); + private final List<String> languagesToConsider = Lists.newArrayList(); + private final String forcedLanguage; + + LanguageDetection(Settings settings, LanguagesRepository languages) { + for (Language language : languages.all()) { + String[] filePatterns = settings.getStringArray(getFileLangPatternPropKey(language.key())); + PathPattern[] pathPatterns = PathPattern.create(filePatterns); + if (pathPatterns.length > 0) { + patternsByLanguage.put(language.key(), pathPatterns); + } else { + // If no custom language pattern is defined then fallback to suffixes declared by language + String[] patterns = language.fileSuffixes().toArray(new String[language.fileSuffixes().size()]); + for (int i = 0; i < patterns.length; i++) { + String suffix = patterns[i]; + String extension = sanitizeExtension(suffix); + patterns[i] = new StringBuilder().append("**/*.").append(extension).toString(); + } + PathPattern[] defaultLanguagePatterns = PathPattern.create(patterns); + patternsByLanguage.put(language.key(), defaultLanguagePatterns); + LOG.debug("Declared extensions of language " + language + " were converted to " + getDetails(language.key())); + } + } + + forcedLanguage = StringUtils.defaultIfBlank(settings.getString(CoreProperties.PROJECT_LANGUAGE_PROPERTY), null); + // First try with lang patterns + if (forcedLanguage != null) { + if (!patternsByLanguage.containsKey(forcedLanguage)) { + throw MessageException.of("No language is installed with key '" + forcedLanguage + "'. Please update property '" + CoreProperties.PROJECT_LANGUAGE_PROPERTY + "'"); + } + languagesToConsider.add(forcedLanguage); + } else { + languagesToConsider.addAll(patternsByLanguage.keySet()); + } + } + + Map<String, PathPattern[]> patternsByLanguage() { + return patternsByLanguage; + } + + @CheckForNull + String language(InputFile inputFile) { + String detectedLanguage = null; + for (String languageKey : languagesToConsider) { + if (isCandidateForLanguage(inputFile, languageKey)) { + if (detectedLanguage == null) { + detectedLanguage = languageKey; + } else { + // Language was already forced by another pattern + throw MessageException.of(MessageFormat.format("Language of file ''{0}'' can not be decided as the file matches patterns of both {1} and {2}", + inputFile.relativePath(), getDetails(detectedLanguage), getDetails(languageKey))); + } + } + } + if (detectedLanguage != null) { + LOG.debug(String.format("Language of file '%s' is detected to be '%s'", inputFile.relativePath(), detectedLanguage)); + return detectedLanguage; + } + + // Check if deprecated sonar.language is used and we are on a language without declared extensions. + // Languages without declared suffixes match everything. + if (forcedLanguage != null && patternsByLanguage.get(forcedLanguage).length == 0) { + return forcedLanguage; + } + return null; + } + + private boolean isCandidateForLanguage(InputFile inputFile, String languageKey) { + PathPattern[] patterns = patternsByLanguage.get(languageKey); + if (patterns != null) { + for (PathPattern pathPattern : patterns) { + if (pathPattern.match(inputFile, false)) { + return true; + } + } + } + return false; + } + + private String getFileLangPatternPropKey(String languageKey) { + return "sonar.lang.patterns." + languageKey; + } + + private String getDetails(String detectedLanguage) { + return getFileLangPatternPropKey(detectedLanguage) + " : " + Joiner.on(",").join(patternsByLanguage.get(detectedLanguage)); + } + + static String sanitizeExtension(String suffix) { + return StringUtils.lowerCase(StringUtils.removeStart(suffix, ".")); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/LanguageDetectionFactory.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/LanguageDetectionFactory.java new file mode 100644 index 00000000000..b25a0038c64 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/LanguageDetectionFactory.java @@ -0,0 +1,39 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.sonar.api.batch.BatchSide; +import org.sonar.api.config.Settings; +import org.sonar.batch.repository.language.LanguagesRepository; + +@BatchSide +public class LanguageDetectionFactory { + private final Settings settings; + private final LanguagesRepository languages; + + public LanguageDetectionFactory(Settings settings, LanguagesRepository languages) { + this.settings = settings; + this.languages = languages; + } + + public LanguageDetection create() { + return new LanguageDetection(settings, languages); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/ModuleFileSystemInitializer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/ModuleFileSystemInitializer.java new file mode 100644 index 00000000000..faab666579a --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/ModuleFileSystemInitializer.java @@ -0,0 +1,122 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import com.google.common.collect.Lists; +import org.apache.commons.io.FileUtils; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.scan.filesystem.PathResolver; +import org.sonar.api.utils.TempFolder; + +import javax.annotation.CheckForNull; + +import java.io.File; +import java.util.List; + +/** + * @since 3.5 + */ +@BatchSide +public class ModuleFileSystemInitializer { + + private File baseDir; + private File workingDir; + private File buildDir; + private List<File> sourceDirsOrFiles = Lists.newArrayList(); + private List<File> testDirsOrFiles = Lists.newArrayList(); + private List<File> binaryDirs = Lists.newArrayList(); + + public ModuleFileSystemInitializer(ProjectDefinition module, TempFolder tempUtils, PathResolver pathResolver) { + baseDir = module.getBaseDir(); + buildDir = module.getBuildDir(); + initWorkingDir(module, tempUtils); + initBinaryDirs(module, pathResolver); + initSources(module, pathResolver); + initTests(module, pathResolver); + } + + private void initWorkingDir(ProjectDefinition module, TempFolder tempUtils) { + workingDir = module.getWorkDir(); + if (workingDir == null) { + workingDir = tempUtils.newDir("work"); + } else { + try { + FileUtils.forceMkdir(workingDir); + } catch (Exception e) { + throw new IllegalStateException("Fail to create working dir: " + workingDir.getAbsolutePath(), e); + } + } + } + + private void initSources(ProjectDefinition module, PathResolver pathResolver) { + for (String sourcePath : module.sources()) { + File dirOrFile = pathResolver.relativeFile(module.getBaseDir(), sourcePath); + if (dirOrFile.exists()) { + sourceDirsOrFiles.add(dirOrFile); + } + } + } + + private void initTests(ProjectDefinition module, PathResolver pathResolver) { + for (String testPath : module.tests()) { + File dirOrFile = pathResolver.relativeFile(module.getBaseDir(), testPath); + if (dirOrFile.exists()) { + testDirsOrFiles.add(dirOrFile); + } + } + } + + private void initBinaryDirs(ProjectDefinition module, PathResolver pathResolver) { + for (String path : module.getBinaries()) { + File dir = pathResolver.relativeFile(module.getBaseDir(), path); + binaryDirs.add(dir); + } + } + + File baseDir() { + return baseDir; + } + + File workingDir() { + return workingDir; + } + + @CheckForNull + File buildDir() { + return buildDir; + } + + List<File> sources() { + return sourceDirsOrFiles; + } + + List<File> tests() { + return testDirsOrFiles; + } + + /** + * @deprecated since 4.5.1 use SonarQube Java specific API + */ + @Deprecated + List<File> binaryDirs() { + return binaryDirs; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/ModuleInputFileCache.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/ModuleInputFileCache.java new file mode 100644 index 00000000000..5326b3fda4b --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/ModuleInputFileCache.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.fs.InputDir; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultFileSystem; + +@BatchSide +public class ModuleInputFileCache extends DefaultFileSystem.Cache { + + private final String moduleKey; + private final InputPathCache inputPathCache; + + public ModuleInputFileCache(ProjectDefinition projectDef, InputPathCache projectCache) { + this.moduleKey = projectDef.getKeyWithBranch(); + this.inputPathCache = projectCache; + } + + @Override + public Iterable<InputFile> inputFiles() { + return inputPathCache.filesByModule(moduleKey); + } + + @Override + public InputFile inputFile(String relativePath) { + return inputPathCache.getFile(moduleKey, relativePath); + } + + @Override + public InputDir inputDir(String relativePath) { + return inputPathCache.getDir(moduleKey, relativePath); + } + + @Override + protected void doAdd(InputFile inputFile) { + inputPathCache.put(moduleKey, inputFile); + } + + @Override + protected void doAdd(InputDir inputDir) { + inputPathCache.put(moduleKey, inputDir); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/StatusDetection.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/StatusDetection.java new file mode 100644 index 00000000000..bd63a81dfe2 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/StatusDetection.java @@ -0,0 +1,50 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.sonar.batch.repository.FileData; + +import org.sonar.batch.repository.ProjectRepositories; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.batch.fs.InputFile; + +class StatusDetection { + + private final ProjectRepositories projectSettings; + + StatusDetection(ProjectRepositories projectSettings) { + this.projectSettings = projectSettings; + } + + InputFile.Status status(String projectKey, String relativePath, String hash) { + FileData fileDataPerPath = projectSettings.fileData(projectKey, relativePath); + if (fileDataPerPath == null) { + return InputFile.Status.ADDED; + } + String previousHash = fileDataPerPath.hash(); + if (StringUtils.equals(hash, previousHash)) { + return InputFile.Status.SAME; + } + if (StringUtils.isEmpty(previousHash)) { + return InputFile.Status.ADDED; + } + return InputFile.Status.CHANGED; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/StatusDetectionFactory.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/StatusDetectionFactory.java new file mode 100644 index 00000000000..b1a75c228c0 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/StatusDetectionFactory.java @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.sonar.batch.repository.ProjectRepositories; + +import org.sonar.api.batch.BatchSide; + +@BatchSide +public class StatusDetectionFactory { + + private final ProjectRepositories projectReferentials; + + public StatusDetectionFactory(ProjectRepositories projectReferentials) { + this.projectReferentials = projectReferentials; + } + + StatusDetection create() { + return new StatusDetection(projectReferentials); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/package-info.java new file mode 100644 index 00000000000..d5f662e81ea --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/filesystem/package-info.java @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +/** + * This package is a part of bootstrap process, so we should take care about backward compatibility. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.scan.filesystem; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/measure/DefaultMetricFinder.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/measure/DefaultMetricFinder.java new file mode 100644 index 00000000000..f453f1bb5f4 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/measure/DefaultMetricFinder.java @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.measure; + +import com.google.common.collect.Lists; +import org.sonar.api.batch.measure.Metric; +import org.sonar.api.batch.measure.MetricFinder; +import org.sonar.api.measures.Metric.ValueType; +import org.sonar.scanner.protocol.input.GlobalRepositories; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class DefaultMetricFinder implements MetricFinder { + + private Map<String, Metric> metricsByKey = new LinkedHashMap<>(); + + public DefaultMetricFinder(GlobalRepositories globalReferentials) { + for (org.sonar.scanner.protocol.input.Metric metric : globalReferentials.metrics()) { + metricsByKey.put(metric.key(), new org.sonar.api.measures.Metric.Builder(metric.key(), metric.key(), ValueType.valueOf(metric.valueType())).create()); + } + } + + @Override + public Metric findByKey(String key) { + return metricsByKey.get(key); + } + + @Override + public Collection<Metric> findAll(List<String> metricKeys) { + List<Metric> result = Lists.newLinkedList(); + for (String metricKey : metricKeys) { + Metric metric = findByKey(metricKey); + if (metric != null) { + result.add(metric); + } + } + return result; + } + + @Override + public Collection<Metric> findAll() { + return metricsByKey.values(); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/measure/DeprecatedMetricFinder.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/measure/DeprecatedMetricFinder.java new file mode 100644 index 00000000000..09e6063355c --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/measure/DeprecatedMetricFinder.java @@ -0,0 +1,80 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.measure; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.sonar.api.measures.Metric; +import org.sonar.api.measures.Metric.ValueType; +import org.sonar.scanner.protocol.input.GlobalRepositories; +import org.sonar.api.measures.MetricFinder; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +public final class DeprecatedMetricFinder implements MetricFinder { + + private Map<String, Metric> metricsByKey = Maps.newLinkedHashMap(); + private Map<Integer, Metric> metricsById = Maps.newLinkedHashMap(); + + public DeprecatedMetricFinder(GlobalRepositories globalReferentials) { + for (org.sonar.scanner.protocol.input.Metric metric : globalReferentials.metrics()) { + Metric hibernateMetric = new org.sonar.api.measures.Metric.Builder(metric.key(), metric.name(), ValueType.valueOf(metric.valueType())) + .create() + .setDirection(metric.direction()) + .setQualitative(metric.isQualitative()) + .setUserManaged(metric.isUserManaged()) + .setDescription(metric.description()) + .setOptimizedBestValue(metric.isOptimizedBestValue()) + .setBestValue(metric.bestValue()) + .setWorstValue(metric.worstValue()) + .setId(metric.id()); + metricsByKey.put(metric.key(), hibernateMetric); + metricsById.put(metric.id(), new org.sonar.api.measures.Metric.Builder(metric.key(), metric.key(), ValueType.valueOf(metric.valueType())).create().setId(metric.id())); + } + } + + @Override + public Metric findById(int metricId) { + return metricsById.get(metricId); + } + + @Override + public Metric findByKey(String key) { + return metricsByKey.get(key); + } + + @Override + public Collection<Metric> findAll(List<String> metricKeys) { + List<Metric> result = Lists.newLinkedList(); + for (String metricKey : metricKeys) { + Metric metric = findByKey(metricKey); + if (metric != null) { + result.add(metric); + } + } + return result; + } + + @Override + public Collection<Metric> findAll() { + return metricsByKey.values(); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/measure/MeasureCache.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/measure/MeasureCache.java new file mode 100644 index 00000000000..963f7e14b91 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/measure/MeasureCache.java @@ -0,0 +1,84 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.measure; + +import com.google.common.base.Preconditions; +import javax.annotation.CheckForNull; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.measure.MetricFinder; +import org.sonar.api.measures.Measure; +import org.sonar.api.resources.Resource; +import org.sonar.batch.index.Cache; +import org.sonar.batch.index.Cache.Entry; +import org.sonar.batch.index.Caches; + +/** + * Cache of all measures. This cache is shared amongst all project modules. + */ +@BatchSide +public class MeasureCache { + + private final Cache<Measure> cache; + + public MeasureCache(Caches caches, MetricFinder metricFinder) { + caches.registerValueCoder(Measure.class, new MeasureValueCoder(metricFinder)); + cache = caches.createCache("measures"); + } + + public Iterable<Entry<Measure>> entries() { + return cache.entries(); + } + + public Iterable<Measure> all() { + return cache.values(); + } + + public Iterable<Measure> byResource(Resource r) { + return byComponentKey(r.getEffectiveKey()); + } + + public Iterable<Measure> byComponentKey(String effectiveKey) { + return cache.values(effectiveKey); + } + + @CheckForNull + public Measure byMetric(Resource r, String metricKey) { + return byMetric(r.getEffectiveKey(), metricKey); + } + + @CheckForNull + public Measure byMetric(String resourceKey, String metricKey) { + return cache.get(resourceKey, metricKey); + } + + public MeasureCache put(Resource resource, Measure measure) { + Preconditions.checkNotNull(resource.getEffectiveKey()); + Preconditions.checkNotNull(measure.getMetricKey()); + cache.put(resource.getEffectiveKey(), measure.getMetricKey(), measure); + return this; + } + + public boolean contains(Resource resource, Measure measure) { + Preconditions.checkNotNull(resource.getEffectiveKey()); + Preconditions.checkNotNull(measure.getMetricKey()); + return cache.containsKey(resource.getEffectiveKey(), measure.getMetricKey()); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/measure/MeasureValueCoder.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/measure/MeasureValueCoder.java new file mode 100644 index 00000000000..019410fd06b --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/measure/MeasureValueCoder.java @@ -0,0 +1,94 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.measure; + +import com.persistit.Value; +import com.persistit.encoding.CoderContext; +import com.persistit.encoding.ValueCoder; +import javax.annotation.Nullable; +import org.sonar.api.batch.measure.MetricFinder; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.Metric; +import org.sonar.api.measures.PersistenceMode; + +class MeasureValueCoder implements ValueCoder { + + private final MetricFinder metricFinder; + + public MeasureValueCoder(MetricFinder metricFinder) { + this.metricFinder = metricFinder; + } + + @Override + public void put(Value value, Object object, CoderContext context) { + Measure<?> m = (Measure) object; + value.putUTF(m.getMetricKey()); + value.put(m.getValue()); + putUTFOrNull(value, m.getData()); + putUTFOrNull(value, m.getDescription()); + value.putString(m.getAlertStatus() != null ? m.getAlertStatus().name() : null); + putUTFOrNull(value, m.getAlertText()); + value.putDate(m.getDate()); + value.put(m.getVariation1()); + value.put(m.getVariation2()); + value.put(m.getVariation3()); + value.put(m.getVariation4()); + value.put(m.getVariation5()); + putUTFOrNull(value, m.getUrl()); + Integer personId = m.getPersonId(); + value.put(personId != null ? personId.intValue() : null); + PersistenceMode persistenceMode = m.getPersistenceMode(); + value.putString(persistenceMode != null ? persistenceMode.name() : null); + } + + private static void putUTFOrNull(Value value, @Nullable String utfOrNull) { + if (utfOrNull != null) { + value.putUTF(utfOrNull); + } else { + value.putNull(); + } + } + + @Override + public Object get(Value value, Class clazz, CoderContext context) { + Measure<?> m = new Measure(); + String metricKey = value.getString(); + org.sonar.api.batch.measure.Metric metric = metricFinder.findByKey(metricKey); + if (metric == null) { + throw new IllegalStateException("Unknow metric with key " + metricKey); + } + m.setMetric((org.sonar.api.measures.Metric) metric); + m.setRawValue(value.isNull(true) ? null : value.getDouble()); + m.setData(value.getString()); + m.setDescription(value.getString()); + m.setAlertStatus(value.isNull(true) ? null : Metric.Level.valueOf(value.getString())); + m.setAlertText(value.getString()); + m.setDate(value.getDate()); + m.setVariation1(value.isNull(true) ? null : value.getDouble()); + m.setVariation2(value.isNull(true) ? null : value.getDouble()); + m.setVariation3(value.isNull(true) ? null : value.getDouble()); + m.setVariation4(value.isNull(true) ? null : value.getDouble()); + m.setVariation5(value.isNull(true) ? null : value.getDouble()); + m.setUrl(value.getString()); + m.setPersonId(value.isNull(true) ? null : value.getInt()); + m.setPersistenceMode(value.isNull(true) ? null : PersistenceMode.valueOf(value.getString())); + return m; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/measure/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/measure/package-info.java new file mode 100644 index 00000000000..f35eb86c95c --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/measure/package-info.java @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +/** + * This package is a part of bootstrap process, so we should take care about backward compatibility. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.scan.measure; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/package-info.java new file mode 100644 index 00000000000..af539896111 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.scan; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/ConsoleReport.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/ConsoleReport.java new file mode 100644 index 00000000000..7e37b789c71 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/ConsoleReport.java @@ -0,0 +1,143 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.report; + +import com.google.common.annotations.VisibleForTesting; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.Properties; +import org.sonar.api.Property; +import org.sonar.api.PropertyType; +import org.sonar.api.config.Settings; +import org.sonar.api.rule.Severity; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.batch.issue.IssueCache; +import org.sonar.batch.issue.tracking.TrackedIssue; +import org.sonar.batch.scan.filesystem.InputPathCache; + +@Properties({ + @Property(key = ConsoleReport.CONSOLE_REPORT_ENABLED_KEY, defaultValue = "false", name = "Enable console report", + description = "Set this to true to generate a report in console output", type = PropertyType.BOOLEAN)}) +public class ConsoleReport implements Reporter { + + @VisibleForTesting + public static final String HEADER = "------------- Issues Report -------------"; + + private static final Logger LOG = Loggers.get(ConsoleReport.class); + + public static final String CONSOLE_REPORT_ENABLED_KEY = "sonar.issuesReport.console.enable"; + private static final int LEFT_PAD = 10; + + private Settings settings; + private IssueCache issueCache; + private InputPathCache inputPathCache; + + @VisibleForTesting + public ConsoleReport(Settings settings, IssueCache issueCache, InputPathCache inputPathCache) { + this.settings = settings; + this.issueCache = issueCache; + this.inputPathCache = inputPathCache; + } + + private static class Report { + boolean noFile = false; + int totalNewIssues = 0; + int newBlockerIssues = 0; + int newCriticalIssues = 0; + int newMajorIssues = 0; + int newMinorIssues = 0; + int newInfoIssues = 0; + + public void process(TrackedIssue issue) { + if (issue.isNew()) { + totalNewIssues++; + switch (issue.severity()) { + case Severity.BLOCKER: + newBlockerIssues++; + break; + case Severity.CRITICAL: + newCriticalIssues++; + break; + case Severity.MAJOR: + newMajorIssues++; + break; + case Severity.MINOR: + newMinorIssues++; + break; + case Severity.INFO: + newInfoIssues++; + break; + default: + throw new IllegalStateException("Unknow severity: " + issue.severity()); + } + } + } + + public void setNoFile(boolean value) { + this.noFile = value; + } + } + + @Override + public void execute() { + if (settings.getBoolean(CONSOLE_REPORT_ENABLED_KEY)) { + Report r = new Report(); + r.setNoFile(!inputPathCache.allFiles().iterator().hasNext()); + for (TrackedIssue issue : issueCache.all()) { + r.process(issue); + } + printReport(r); + } + } + + public void printReport(Report r) { + StringBuilder sb = new StringBuilder(); + + sb.append("\n\n" + HEADER + "\n\n"); + if (r.noFile) { + sb.append(" No file analyzed\n"); + } else { + printNewIssues(r, sb); + } + sb.append("\n-------------------------------------------\n\n"); + + LOG.info(sb.toString()); + } + + private static void printNewIssues(Report r, StringBuilder sb) { + int newIssues = r.totalNewIssues; + if (newIssues > 0) { + sb.append(StringUtils.leftPad("+" + newIssues, LEFT_PAD)).append(" issue" + (newIssues > 1 ? "s" : "")).append("\n\n"); + printNewIssues(sb, r.newBlockerIssues, Severity.BLOCKER, "blocker"); + printNewIssues(sb, r.newCriticalIssues, Severity.CRITICAL, "critical"); + printNewIssues(sb, r.newMajorIssues, Severity.MAJOR, "major"); + printNewIssues(sb, r.newMinorIssues, Severity.MINOR, "minor"); + printNewIssues(sb, r.newInfoIssues, Severity.INFO, "info"); + } else { + sb.append(" No new issue").append("\n"); + } + } + + private static void printNewIssues(StringBuilder sb, int issueCount, String severity, String severityLabel) { + if (issueCount > 0) { + sb.append(StringUtils.leftPad("+" + issueCount, LEFT_PAD)).append(" ").append(severityLabel).append("\n"); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/HtmlReport.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/HtmlReport.java new file mode 100644 index 00000000000..8111abd467e --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/HtmlReport.java @@ -0,0 +1,179 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.report; + +import com.google.common.collect.Maps; +import freemarker.template.Template; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.net.URISyntaxException; +import java.util.Map; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.Properties; +import org.sonar.api.Property; +import org.sonar.api.PropertyType; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.config.Settings; + +@Properties({ + @Property(key = HtmlReport.HTML_REPORT_ENABLED_KEY, defaultValue = "false", name = "Enable HTML report", description = "Set this to true to generate an HTML report", + type = PropertyType.BOOLEAN), + @Property(key = HtmlReport.HTML_REPORT_LOCATION_KEY, defaultValue = HtmlReport.HTML_REPORT_LOCATION_DEFAULT, name = "HTML Report location", + description = "Location of the generated report. Can be absolute or relative to working directory", project = false, global = false, type = PropertyType.STRING), + @Property(key = HtmlReport.HTML_REPORT_NAME_KEY, defaultValue = HtmlReport.HTML_REPORT_NAME_DEFAULT, name = "HTML Report name", + description = "Name of the generated report. Will be suffixed by .html or -light.html", project = false, global = false, type = PropertyType.STRING), + @Property(key = HtmlReport.HTML_REPORT_LIGHTMODE_ONLY, defaultValue = "false", name = "Html report in light mode only", + description = "Set this to true to only generate the new issues report (light report)", project = true, type = PropertyType.BOOLEAN)}) +public class HtmlReport implements Reporter { + private static final Logger LOG = LoggerFactory.getLogger(HtmlReport.class); + + public static final String HTML_REPORT_ENABLED_KEY = "sonar.issuesReport.html.enable"; + public static final String HTML_REPORT_LOCATION_KEY = "sonar.issuesReport.html.location"; + public static final String HTML_REPORT_LOCATION_DEFAULT = "issues-report"; + public static final String HTML_REPORT_NAME_KEY = "sonar.issuesReport.html.name"; + public static final String HTML_REPORT_NAME_DEFAULT = "issues-report"; + public static final String HTML_REPORT_LIGHTMODE_ONLY = "sonar.issuesReport.lightModeOnly"; + + private final Settings settings; + private final FileSystem fs; + private final IssuesReportBuilder builder; + private final SourceProvider sourceProvider; + private final RuleNameProvider ruleNameProvider; + + public HtmlReport(Settings settings, FileSystem fs, IssuesReportBuilder builder, SourceProvider sourceProvider, RuleNameProvider ruleNameProvider) { + this.settings = settings; + this.fs = fs; + this.builder = builder; + this.sourceProvider = sourceProvider; + this.ruleNameProvider = ruleNameProvider; + } + + @Override + public void execute() { + if (settings.getBoolean(HTML_REPORT_ENABLED_KEY)) { + IssuesReport report = builder.buildReport(); + print(report); + } + } + + public void print(IssuesReport report) { + File reportFileDir = getReportFileDir(); + String reportName = settings.getString(HTML_REPORT_NAME_KEY); + if (!isLightModeOnly()) { + File reportFile = new File(reportFileDir, reportName + ".html"); + LOG.debug("Generating HTML Report to: " + reportFile.getAbsolutePath()); + writeToFile(report, reportFile, true); + LOG.info("HTML Issues Report generated: " + reportFile.getAbsolutePath()); + } + File lightReportFile = new File(reportFileDir, reportName + "-light.html"); + LOG.debug("Generating Light HTML Report to: " + lightReportFile.getAbsolutePath()); + writeToFile(report, lightReportFile, false); + LOG.info("Light HTML Issues Report generated: " + lightReportFile.getAbsolutePath()); + try { + copyDependencies(reportFileDir); + } catch (Exception e) { + throw new IllegalStateException("Fail to copy HTML report resources to: " + reportFileDir, e); + } + } + + private File getReportFileDir() { + String reportFileDirStr = settings.getString(HTML_REPORT_LOCATION_KEY); + File reportFileDir = new File(reportFileDirStr); + if (!reportFileDir.isAbsolute()) { + reportFileDir = new File(fs.workDir(), reportFileDirStr); + } + if (StringUtils.endsWith(reportFileDirStr, ".html")) { + LOG.warn(HTML_REPORT_LOCATION_KEY + " should indicate a directory. Using parent folder."); + reportFileDir = reportFileDir.getParentFile(); + } + try { + FileUtils.forceMkdir(reportFileDir); + } catch (IOException e) { + throw new IllegalStateException("Fail to create the directory " + reportFileDirStr, e); + } + return reportFileDir; + } + + public void writeToFile(IssuesReport report, File toFile, boolean complete) { + try { + freemarker.log.Logger.selectLoggerLibrary(freemarker.log.Logger.LIBRARY_NONE); + freemarker.template.Configuration cfg = new freemarker.template.Configuration(); + cfg.setClassForTemplateLoading(HtmlReport.class, ""); + + Map<String, Object> root = Maps.newHashMap(); + root.put("report", report); + root.put("ruleNameProvider", ruleNameProvider); + root.put("sourceProvider", sourceProvider); + root.put("complete", complete); + + Template template = cfg.getTemplate("issuesreport.ftl"); + + try (FileOutputStream fos = new FileOutputStream(toFile); Writer writer = new OutputStreamWriter(fos, fs.encoding())) { + template.process(root, writer); + writer.flush(); + } + } catch (Exception e) { + throw new IllegalStateException("Fail to generate HTML Issues Report to: " + toFile, e); + + } + } + + void copyDependencies(File toDir) throws URISyntaxException, IOException { + File target = new File(toDir, "issuesreport_files"); + FileUtils.forceMkdir(target); + + // I don't know how to extract a directory from classpath, that's why an exhaustive list of files + // is provided here : + copyDependency(target, "sonar.eot"); + copyDependency(target, "sonar.svg"); + copyDependency(target, "sonar.ttf"); + copyDependency(target, "sonar.woff"); + copyDependency(target, "favicon.ico"); + copyDependency(target, "PRJ.png"); + copyDependency(target, "DIR.png"); + copyDependency(target, "FIL.png"); + copyDependency(target, "jquery.min.js"); + copyDependency(target, "sep12.png"); + copyDependency(target, "sonar.css"); + copyDependency(target, "sonarqube-24x100.png"); + } + + private void copyDependency(File target, String filename) { + try (InputStream input = getClass().getResourceAsStream("/org/sonar/batch/scan/report/issuesreport_files/" + filename); + OutputStream output = new FileOutputStream(new File(target, filename))) { + IOUtils.copy(input, output); + } catch (IOException e) { + throw new IllegalStateException("Fail to copy file " + filename + " to " + target, e); + } + } + + public boolean isLightModeOnly() { + return settings.getBoolean(HTML_REPORT_LIGHTMODE_ONLY); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/IssueVariation.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/IssueVariation.java new file mode 100644 index 00000000000..23ca58e7d8c --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/IssueVariation.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.report; + +import org.apache.commons.lang.builder.ToStringBuilder; + +public class IssueVariation { + + private int countInCurrentAnalysis; + private int newIssuesCount; + private int resolvedIssuesCount; + + public int getCountInCurrentAnalysis() { + return countInCurrentAnalysis; + } + + public void incrementCountInCurrentAnalysis() { + this.countInCurrentAnalysis++; + } + + public int getNewIssuesCount() { + return newIssuesCount; + } + + public void incrementNewIssuesCount() { + this.newIssuesCount++; + } + + public int getResolvedIssuesCount() { + return resolvedIssuesCount; + } + + public void incrementResolvedIssuesCount() { + this.resolvedIssuesCount++; + } + + @Override + public String toString() { + return new ToStringBuilder(this). + append("countInCurrentAnalysis", countInCurrentAnalysis). + append("newIssuesCount", newIssuesCount). + append("resolvedIssuesCount", resolvedIssuesCount). + toString(); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/IssuesReport.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/IssuesReport.java new file mode 100644 index 00000000000..cede7a81d1c --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/IssuesReport.java @@ -0,0 +1,102 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.report; + +import org.sonar.batch.issue.tracking.TrackedIssue; + +import com.google.common.collect.Maps; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.sonar.api.batch.rule.Rule; +import org.sonar.api.rules.RulePriority; +import org.sonar.batch.index.BatchComponent; + +public class IssuesReport { + + public static final int TOO_MANY_ISSUES_THRESHOLD = 1000; + private String title; + private Date date; + private boolean noFile; + private final ReportSummary summary = new ReportSummary(); + private final Map<BatchComponent, ResourceReport> resourceReportsByResource = Maps.newLinkedHashMap(); + + public ReportSummary getSummary() { + return summary; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + public boolean isNoFile() { + return noFile; + } + + public void setNoFile(boolean noFile) { + this.noFile = noFile; + } + + public Map<BatchComponent, ResourceReport> getResourceReportsByResource() { + return resourceReportsByResource; + } + + public List<ResourceReport> getResourceReports() { + return new ArrayList<>(resourceReportsByResource.values()); + } + + public List<BatchComponent> getResourcesWithReport() { + return new ArrayList<>(resourceReportsByResource.keySet()); + } + + public void addIssueOnResource(BatchComponent resource, TrackedIssue issue, Rule rule, RulePriority severity) { + addResource(resource); + getSummary().addIssue(issue, rule, severity); + resourceReportsByResource.get(resource).addIssue(issue, rule, RulePriority.valueOf(issue.severity())); + } + + public void addResolvedIssueOnResource(BatchComponent resource, TrackedIssue issue, Rule rule, RulePriority severity) { + addResource(resource); + getSummary().addResolvedIssue(issue, rule, severity); + resourceReportsByResource.get(resource).addResolvedIssue(rule, RulePriority.valueOf(issue.severity())); + } + + private void addResource(BatchComponent resource) { + if (!resourceReportsByResource.containsKey(resource)) { + resourceReportsByResource.put(resource, new ResourceReport(resource)); + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/IssuesReportBuilder.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/IssuesReportBuilder.java new file mode 100644 index 00000000000..079dabc2ceb --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/IssuesReportBuilder.java @@ -0,0 +1,103 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.report; + +import org.sonar.batch.issue.tracking.TrackedIssue; + +import javax.annotation.CheckForNull; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.rule.Rule; +import org.sonar.api.batch.rule.Rules; +import org.sonar.api.resources.Project; +import org.sonar.api.rules.RulePriority; +import org.sonar.batch.DefaultProjectTree; +import org.sonar.batch.index.BatchComponent; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.issue.IssueCache; +import org.sonar.batch.scan.filesystem.InputPathCache; + +@BatchSide +public class IssuesReportBuilder { + + private static final Logger LOG = LoggerFactory.getLogger(IssuesReportBuilder.class); + + private final IssueCache issueCache; + private final Rules rules; + private final BatchComponentCache resourceCache; + private final DefaultProjectTree projectTree; + private final InputPathCache inputPathCache; + + public IssuesReportBuilder(IssueCache issueCache, Rules rules, BatchComponentCache resourceCache, DefaultProjectTree projectTree, InputPathCache inputPathCache) { + this.issueCache = issueCache; + this.rules = rules; + this.resourceCache = resourceCache; + this.projectTree = projectTree; + this.inputPathCache = inputPathCache; + } + + public IssuesReport buildReport() { + Project project = projectTree.getRootProject(); + IssuesReport issuesReport = new IssuesReport(); + issuesReport.setNoFile(!inputPathCache.allFiles().iterator().hasNext()); + issuesReport.setTitle(project.getName()); + issuesReport.setDate(project.getAnalysisDate()); + + processIssues(issuesReport, issueCache.all()); + + return issuesReport; + } + + private void processIssues(IssuesReport issuesReport, Iterable<TrackedIssue> issues) { + for (TrackedIssue issue : issues) { + Rule rule = findRule(issue); + RulePriority severity = RulePriority.valueOf(issue.severity()); + BatchComponent resource = resourceCache.get(issue.componentKey()); + if (!validate(issue, rule, resource)) { + continue; + } + if (issue.resolution() != null) { + issuesReport.addResolvedIssueOnResource(resource, issue, rule, severity); + } else { + issuesReport.addIssueOnResource(resource, issue, rule, severity); + } + } + } + + private static boolean validate(TrackedIssue issue, Rule rule, BatchComponent resource) { + if (rule == null) { + LOG.warn("Unknow rule for issue {}", issue); + return false; + } + if (resource == null) { + LOG.debug("Unknow resource with key {}", issue.componentKey()); + return false; + } + return true; + } + + @CheckForNull + private Rule findRule(TrackedIssue issue) { + return rules.find(issue.getRuleKey()); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/IssuesReports.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/IssuesReports.java new file mode 100644 index 00000000000..ac4a5abb375 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/IssuesReports.java @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.report; + +import org.sonar.api.batch.BatchSide; + +@BatchSide +public class IssuesReports { + + private final Reporter[] reporters; + + public IssuesReports(Reporter... reporters) { + this.reporters = reporters; + } + + public void execute() { + for (Reporter reporter : reporters) { + reporter.execute(); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/JSONReport.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/JSONReport.java new file mode 100644 index 00000000000..99954736613 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/JSONReport.java @@ -0,0 +1,247 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.report; + +import com.google.common.annotations.VisibleForTesting; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.Properties; +import org.sonar.api.Property; +import org.sonar.api.PropertyType; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputDir; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputDir; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.rule.Rule; +import org.sonar.api.batch.rule.Rules; +import org.sonar.api.config.Settings; +import org.sonar.api.platform.Server; +import org.sonar.api.resources.Project; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.utils.text.JsonWriter; +import org.sonar.batch.issue.IssueCache; +import org.sonar.batch.issue.tracking.TrackedIssue; +import org.sonar.batch.repository.user.UserRepositoryLoader; +import org.sonar.batch.scan.filesystem.InputPathCache; +import org.sonar.scanner.protocol.input.ScannerInput; +import org.sonar.scanner.protocol.input.ScannerInput.User; + +@Properties({ + @Property( + key = JSONReport.SONAR_REPORT_EXPORT_PATH, + name = "Report Results Export File", + type = PropertyType.STRING, + global = false, project = false)}) +public class JSONReport implements Reporter { + + static final String SONAR_REPORT_EXPORT_PATH = "sonar.report.export.path"; + private static final Logger LOG = LoggerFactory.getLogger(JSONReport.class); + private final Settings settings; + private final FileSystem fileSystem; + private final Server server; + private final Rules rules; + private final IssueCache issueCache; + private final InputPathCache fileCache; + private final Project rootModule; + private final UserRepositoryLoader userRepository; + + public JSONReport(Settings settings, FileSystem fileSystem, Server server, Rules rules, IssueCache issueCache, + Project rootModule, InputPathCache fileCache, UserRepositoryLoader userRepository) { + this.settings = settings; + this.fileSystem = fileSystem; + this.server = server; + this.rules = rules; + this.issueCache = issueCache; + this.rootModule = rootModule; + this.fileCache = fileCache; + this.userRepository = userRepository; + } + + @Override + public void execute() { + String exportPath = settings.getString(SONAR_REPORT_EXPORT_PATH); + if (exportPath != null) { + exportResults(exportPath); + } + } + + private void exportResults(String exportPath) { + File exportFile = new File(fileSystem.workDir(), exportPath); + + LOG.info("Export issues to {}", exportFile.getAbsolutePath()); + try (Writer output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(exportFile), StandardCharsets.UTF_8))) { + writeJson(output); + + } catch (IOException e) { + throw new IllegalStateException("Unable to write report results in file " + exportFile.getAbsolutePath(), e); + } + } + + @VisibleForTesting + void writeJson(Writer writer) { + try { + JsonWriter json = JsonWriter.of(writer); + json.beginObject(); + json.prop("version", server.getVersion()); + + Set<RuleKey> ruleKeys = new LinkedHashSet<>(); + Set<String> userLogins = new LinkedHashSet<>(); + writeJsonIssues(json, ruleKeys, userLogins); + writeJsonComponents(json); + writeJsonRules(json, ruleKeys); + writeUsers(json, userLogins); + json.endObject().close(); + + } catch (IOException e) { + throw new IllegalStateException("Unable to write JSON report", e); + } + } + + private void writeJsonIssues(JsonWriter json, Set<RuleKey> ruleKeys, Set<String> logins) throws IOException { + json.name("issues").beginArray(); + for (TrackedIssue issue : getIssues()) { + if (issue.resolution() == null) { + json + .beginObject() + .prop("key", issue.key()) + .prop("component", issue.componentKey()) + .prop("line", issue.startLine()) + .prop("startLine", issue.startLine()) + .prop("startOffset", issue.startLineOffset()) + .prop("endLine", issue.endLine()) + .prop("endOffset", issue.endLineOffset()) + .prop("message", issue.getMessage()) + .prop("severity", issue.severity()) + .prop("rule", issue.getRuleKey().toString()) + .prop("status", issue.status()) + .prop("resolution", issue.resolution()) + .prop("isNew", issue.isNew()) + .prop("assignee", issue.assignee()) + .prop("effortToFix", issue.gap()) + .propDateTime("creationDate", issue.creationDate()); + if (!StringUtils.isEmpty(issue.reporter())) { + logins.add(issue.reporter()); + } + if (!StringUtils.isEmpty(issue.assignee())) { + logins.add(issue.assignee()); + } + json.endObject(); + ruleKeys.add(issue.getRuleKey()); + } + } + json.endArray(); + } + + private void writeJsonComponents(JsonWriter json) throws IOException { + json.name("components").beginArray(); + // Dump modules + writeJsonModuleComponents(json, rootModule); + for (InputFile inputFile : fileCache.allFiles()) { + String key = ((DefaultInputFile) inputFile).key(); + json + .beginObject() + .prop("key", key) + .prop("path", inputFile.relativePath()) + .prop("moduleKey", StringUtils.substringBeforeLast(key, ":")) + .prop("status", inputFile.status().name()) + .endObject(); + } + for (InputDir inputDir : fileCache.allDirs()) { + String key = ((DefaultInputDir) inputDir).key(); + json + .beginObject() + .prop("key", key) + .prop("path", inputDir.relativePath()) + .prop("moduleKey", StringUtils.substringBeforeLast(key, ":")) + .endObject(); + + } + json.endArray(); + } + + private static void writeJsonModuleComponents(JsonWriter json, Project module) { + json + .beginObject() + .prop("key", module.getEffectiveKey()) + .prop("path", module.getPath()) + .endObject(); + for (Project subModule : module.getModules()) { + writeJsonModuleComponents(json, subModule); + } + } + + private void writeJsonRules(JsonWriter json, Set<RuleKey> ruleKeys) throws IOException { + json.name("rules").beginArray(); + for (RuleKey ruleKey : ruleKeys) { + json + .beginObject() + .prop("key", ruleKey.toString()) + .prop("rule", ruleKey.rule()) + .prop("repository", ruleKey.repository()) + .prop("name", getRuleName(ruleKey)) + .endObject(); + } + json.endArray(); + } + + private void writeUsers(JsonWriter json, Collection<String> userLogins) throws IOException { + List<ScannerInput.User> users = new LinkedList<>(); + for (String userLogin : userLogins) { + User user = userRepository.load(userLogin); + if (user != null) { + users.add(user); + } + } + + json.name("users").beginArray(); + for (ScannerInput.User user : users) { + json + .beginObject() + .prop("login", user.getLogin()) + .prop("name", user.getName()) + .endObject(); + } + json.endArray(); + } + + private String getRuleName(RuleKey ruleKey) { + Rule rule = rules.find(ruleKey); + return rule != null ? rule.name() : null; + } + + @VisibleForTesting + Iterable<TrackedIssue> getIssues() { + return issueCache.all(); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/ReportRuleKey.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/ReportRuleKey.java new file mode 100644 index 00000000000..1061d3c3238 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/ReportRuleKey.java @@ -0,0 +1,84 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.report; + +import org.sonar.api.batch.rule.Rule; + +import org.apache.commons.lang.ObjectUtils; +import org.apache.commons.lang.builder.ToStringBuilder; +import org.sonar.api.rules.RulePriority; + +/** + * A same rule can be present with different severity if severity was manually changed so we need this special key that + * include severity. + * + */ +public class ReportRuleKey implements Comparable<ReportRuleKey> { + private final Rule rule; + private final RulePriority severity; + + public ReportRuleKey(Rule rule, RulePriority severity) { + this.rule = rule; + this.severity = severity; + } + + public Rule getRule() { + return rule; + } + + public RulePriority getSeverity() { + return severity; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ReportRuleKey that = (ReportRuleKey) o; + return ObjectUtils.equals(rule, that.rule) && ObjectUtils.equals(severity, that.severity); + } + + @Override + public int hashCode() { + int result = rule.hashCode(); + result = 31 * result + severity.hashCode(); + return result; + } + + @Override + public int compareTo(ReportRuleKey o) { + if (severity == o.getSeverity()) { + return getRule().key().toString().compareTo(o.getRule().key().toString()); + } + return o.getSeverity().compareTo(severity); + } + + @Override + public String toString() { + return new ToStringBuilder(this). + append("rule", rule). + append("severity", severity). + toString(); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/ReportSummary.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/ReportSummary.java new file mode 100644 index 00000000000..435aee37450 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/ReportSummary.java @@ -0,0 +1,92 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.report; + +import com.google.common.collect.Maps; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.sonar.api.batch.rule.Rule; +import org.sonar.api.rules.RulePriority; +import org.sonar.batch.issue.tracking.TrackedIssue; + +public class ReportSummary { + + private final IssueVariation total = new IssueVariation(); + + private final Map<ReportRuleKey, RuleReport> ruleReportByRuleKey = Maps.newLinkedHashMap(); + private final Map<String, IssueVariation> totalByRuleKey = Maps.newLinkedHashMap(); + private final Map<String, IssueVariation> totalBySeverity = Maps.newLinkedHashMap(); + + public IssueVariation getTotal() { + return total; + } + + public void addIssue(TrackedIssue issue, Rule rule, RulePriority severity) { + ReportRuleKey reportRuleKey = new ReportRuleKey(rule, severity); + initMaps(reportRuleKey); + ruleReportByRuleKey.get(reportRuleKey).getTotal().incrementCountInCurrentAnalysis(); + total.incrementCountInCurrentAnalysis(); + totalByRuleKey.get(rule.key().toString()).incrementCountInCurrentAnalysis(); + totalBySeverity.get(severity.toString()).incrementCountInCurrentAnalysis(); + if (issue.isNew()) { + total.incrementNewIssuesCount(); + ruleReportByRuleKey.get(reportRuleKey).getTotal().incrementNewIssuesCount(); + totalByRuleKey.get(rule.key().toString()).incrementNewIssuesCount(); + totalBySeverity.get(severity.toString()).incrementNewIssuesCount(); + } + } + + public Map<String, IssueVariation> getTotalBySeverity() { + return totalBySeverity; + } + + public Map<String, IssueVariation> getTotalByRuleKey() { + return totalByRuleKey; + } + + public void addResolvedIssue(TrackedIssue issue, Rule rule, RulePriority severity) { + ReportRuleKey reportRuleKey = new ReportRuleKey(rule, severity); + initMaps(reportRuleKey); + total.incrementResolvedIssuesCount(); + ruleReportByRuleKey.get(reportRuleKey).getTotal().incrementResolvedIssuesCount(); + totalByRuleKey.get(rule.key().toString()).incrementResolvedIssuesCount(); + totalBySeverity.get(severity.toString()).incrementResolvedIssuesCount(); + } + + private void initMaps(ReportRuleKey reportRuleKey) { + if (!ruleReportByRuleKey.containsKey(reportRuleKey)) { + ruleReportByRuleKey.put(reportRuleKey, new RuleReport(reportRuleKey)); + } + if (!totalByRuleKey.containsKey(reportRuleKey.getRule().key().toString())) { + totalByRuleKey.put(reportRuleKey.getRule().key().toString(), new IssueVariation()); + } + if (!totalBySeverity.containsKey(reportRuleKey.getSeverity().toString())) { + totalBySeverity.put(reportRuleKey.getSeverity().toString(), new IssueVariation()); + } + } + + public List<RuleReport> getRuleReports() { + List<RuleReport> result = new ArrayList<>(ruleReportByRuleKey.values()); + Collections.sort(result, new RuleReportComparator()); + return result; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/Reporter.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/Reporter.java new file mode 100644 index 00000000000..9cb91dc2fad --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/Reporter.java @@ -0,0 +1,29 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.report; + +import org.sonar.api.batch.BatchSide; + +@BatchSide +public interface Reporter { + + void execute(); + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/ResourceReport.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/ResourceReport.java new file mode 100644 index 00000000000..6267335baf0 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/ResourceReport.java @@ -0,0 +1,155 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.report; + +import org.sonar.batch.issue.tracking.TrackedIssue; + +import org.sonar.api.batch.rule.Rule; +import com.google.common.collect.Maps; +import org.sonar.api.rules.RulePriority; +import org.sonar.batch.index.BatchComponent; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +public final class ResourceReport { + private final BatchComponent resource; + private final IssueVariation total = new IssueVariation(); + private final Map<ReportRuleKey, RuleReport> ruleReportByRuleKey = Maps.newHashMap(); + + private List<TrackedIssue> issues = new ArrayList<>(); + private Map<Integer, List<TrackedIssue>> issuesPerLine = Maps.newHashMap(); + private Map<Integer, List<TrackedIssue>> newIssuesPerLine = Maps.newHashMap(); + private Map<Rule, AtomicInteger> issuesByRule = Maps.newHashMap(); + private Map<RulePriority, AtomicInteger> issuesBySeverity = Maps.newHashMap(); + + public ResourceReport(BatchComponent resource) { + this.resource = resource; + } + + public BatchComponent getResourceNode() { + return resource; + } + + public String getName() { + return resource.resource().getName(); + } + + public String getType() { + return resource.resource().getScope(); + } + + public IssueVariation getTotal() { + return total; + } + + public List<TrackedIssue> getIssues() { + return issues; + } + + public Map<Integer, List<TrackedIssue>> getIssuesPerLine() { + return issuesPerLine; + } + + public List<TrackedIssue> getIssuesAtLine(int lineId, boolean all) { + if (all) { + if (issuesPerLine.containsKey(lineId)) { + return issuesPerLine.get(lineId); + } + } else if (newIssuesPerLine.containsKey(lineId)) { + return newIssuesPerLine.get(lineId); + } + return Collections.emptyList(); + } + + public void addIssue(TrackedIssue issue, Rule rule, RulePriority severity) { + ReportRuleKey reportRuleKey = new ReportRuleKey(rule, severity); + initMaps(reportRuleKey); + issues.add(issue); + Integer line = issue.startLine(); + line = line != null ? line : 0; + if (!issuesPerLine.containsKey(line)) { + issuesPerLine.put(line, new ArrayList<TrackedIssue>()); + } + issuesPerLine.get(line).add(issue); + if (!issuesByRule.containsKey(rule)) { + issuesByRule.put(rule, new AtomicInteger()); + } + issuesByRule.get(rule).incrementAndGet(); + if (!issuesBySeverity.containsKey(severity)) { + issuesBySeverity.put(severity, new AtomicInteger()); + } + issuesBySeverity.get(severity).incrementAndGet(); + ruleReportByRuleKey.get(reportRuleKey).getTotal().incrementCountInCurrentAnalysis(); + total.incrementCountInCurrentAnalysis(); + if (issue.isNew()) { + if (!newIssuesPerLine.containsKey(line)) { + newIssuesPerLine.put(line, new ArrayList<TrackedIssue>()); + } + newIssuesPerLine.get(line).add(issue); + total.incrementNewIssuesCount(); + ruleReportByRuleKey.get(reportRuleKey).getTotal().incrementNewIssuesCount(); + } + } + + public void addResolvedIssue(Rule rule, RulePriority severity) { + ReportRuleKey reportRuleKey = new ReportRuleKey(rule, severity); + initMaps(reportRuleKey); + total.incrementResolvedIssuesCount(); + ruleReportByRuleKey.get(reportRuleKey).getTotal().incrementResolvedIssuesCount(); + } + + private void initMaps(ReportRuleKey reportRuleKey) { + if (!ruleReportByRuleKey.containsKey(reportRuleKey)) { + ruleReportByRuleKey.put(reportRuleKey, new RuleReport(reportRuleKey)); + } + } + + public boolean isDisplayableLine(Integer lineNumber, boolean all) { + if (lineNumber == null || lineNumber < 1) { + return false; + } + for (int i = lineNumber - 2; i <= lineNumber + 2; i++) { + if (hasIssues(i, all)) { + return true; + } + } + return false; + } + + private boolean hasIssues(Integer lineId, boolean all) { + if (all) { + List<TrackedIssue> issuesAtLine = issuesPerLine.get(lineId); + return issuesAtLine != null && !issuesAtLine.isEmpty(); + } + List<TrackedIssue> newIssuesAtLine = newIssuesPerLine.get(lineId); + return newIssuesAtLine != null && !newIssuesAtLine.isEmpty(); + } + + public List<RuleReport> getRuleReports() { + List<RuleReport> result = new ArrayList<>(ruleReportByRuleKey.values()); + Collections.sort(result, new RuleReportComparator()); + return result; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/RuleNameProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/RuleNameProvider.java new file mode 100644 index 00000000000..90981fea342 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/RuleNameProvider.java @@ -0,0 +1,59 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.report; + +import org.sonar.api.batch.rule.Rule; + +import org.sonar.api.batch.rule.Rules; +import org.apache.commons.lang.StringEscapeUtils; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.rule.RuleKey; + +import javax.annotation.CheckForNull; + +@BatchSide +public class RuleNameProvider { + private Rules rules; + + public RuleNameProvider(Rules rules) { + this.rules = rules; + } + + @CheckForNull + private String nameFromDB(RuleKey ruleKey) { + Rule r = rules.find(ruleKey); + return r != null ? r.name() : null; + } + + public String nameForHTML(RuleKey ruleKey) { + String name = nameFromDB(ruleKey); + return StringEscapeUtils.escapeHtml(name != null ? name : ruleKey.toString()); + } + + public String nameForJS(String ruleKey) { + String name = nameFromDB(RuleKey.parse(ruleKey)); + return StringEscapeUtils.escapeJavaScript(name != null ? name : ruleKey); + } + + public String nameForHTML(Rule rule) { + return StringEscapeUtils.escapeHtml(rule.name()); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/RuleReport.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/RuleReport.java new file mode 100644 index 00000000000..4026ed387ab --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/RuleReport.java @@ -0,0 +1,57 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.report; + +import org.sonar.api.batch.rule.Rule; + +import org.apache.commons.lang.builder.ToStringBuilder; + +public final class RuleReport { + private final ReportRuleKey reportRuleKey; + private final IssueVariation total = new IssueVariation(); + + public RuleReport(ReportRuleKey reportRuleKey) { + this.reportRuleKey = reportRuleKey; + } + + public IssueVariation getTotal() { + return total; + } + + public ReportRuleKey getReportRuleKey() { + return reportRuleKey; + } + + public String getSeverity() { + return reportRuleKey.getSeverity().toString(); + } + + public Rule getRule() { + return reportRuleKey.getRule(); + } + + @Override + public String toString() { + return new ToStringBuilder(this). + append("reportRuleKey", reportRuleKey). + append("total", total). + toString(); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/RuleReportComparator.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/RuleReportComparator.java new file mode 100644 index 00000000000..b779537e8fd --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/RuleReportComparator.java @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.report; + +import java.io.Serializable; +import java.util.Comparator; + +public class RuleReportComparator implements Comparator<RuleReport>, Serializable { + @Override + public int compare(RuleReport o1, RuleReport o2) { + if (bothHaveNoNewIssue(o1, o2)) { + return compareByRuleSeverityAndName(o1, o2); + } else if (bothHaveNewIssues(o1, o2)) { + if (sameSeverity(o1, o2) && !sameNewIssueCount(o1, o2)) { + return compareNewIssueCount(o1, o2); + } else { + return compareByRuleSeverityAndName(o1, o2); + } + } else { + return compareNewIssueCount(o1, o2); + } + } + + private static int compareByRuleSeverityAndName(RuleReport o1, RuleReport o2) { + return o1.getReportRuleKey().compareTo(o2.getReportRuleKey()); + } + + private static boolean sameNewIssueCount(RuleReport o1, RuleReport o2) { + return o2.getTotal().getNewIssuesCount() == o1.getTotal().getNewIssuesCount(); + } + + private static boolean sameSeverity(RuleReport o1, RuleReport o2) { + return o1.getSeverity().equals(o2.getSeverity()); + } + + private static int compareNewIssueCount(RuleReport o1, RuleReport o2) { + return o2.getTotal().getNewIssuesCount() - o1.getTotal().getNewIssuesCount(); + } + + private static boolean bothHaveNewIssues(RuleReport o1, RuleReport o2) { + return o1.getTotal().getNewIssuesCount() > 0 && o2.getTotal().getNewIssuesCount() > 0; + } + + private static boolean bothHaveNoNewIssue(RuleReport o1, RuleReport o2) { + return o1.getTotal().getNewIssuesCount() == 0 && o2.getTotal().getNewIssuesCount() == 0; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/SourceProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/SourceProvider.java new file mode 100644 index 00000000000..8017dbd753e --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/SourceProvider.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.report; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringEscapeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.batch.index.BatchComponent; + +@BatchSide +public class SourceProvider { + + private static final Logger LOG = LoggerFactory.getLogger(SourceProvider.class); + private final FileSystem fs; + + public SourceProvider(FileSystem fs) { + this.fs = fs; + } + + public List<String> getEscapedSource(BatchComponent component) { + if (!component.isFile()) { + // Folder + return Collections.emptyList(); + } + try { + InputFile inputFile = (InputFile) component.inputComponent(); + List<String> lines = FileUtils.readLines(inputFile.file(), fs.encoding()); + List<String> escapedLines = new ArrayList<>(lines.size()); + for (String line : lines) { + escapedLines.add(StringEscapeUtils.escapeHtml(line)); + } + return escapedLines; + } catch (IOException e) { + LOG.warn("Unable to read source code of resource {}", component, e); + return Collections.emptyList(); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/package-info.java new file mode 100644 index 00000000000..d6dacad1b54 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scan/report/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.scan.report; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scm/DefaultBlameInput.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scm/DefaultBlameInput.java new file mode 100644 index 00000000000..a85c6536c92 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scm/DefaultBlameInput.java @@ -0,0 +1,46 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scm; + +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.scm.BlameCommand.BlameInput; + +class DefaultBlameInput implements BlameInput { + + private FileSystem fs; + private Iterable<InputFile> filesToBlame; + + DefaultBlameInput(FileSystem fs, Iterable<InputFile> filesToBlame) { + this.fs = fs; + this.filesToBlame = filesToBlame; + } + + @Override + public FileSystem fileSystem() { + return fs; + } + + @Override + public Iterable<InputFile> filesToBlame() { + return filesToBlame; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scm/DefaultBlameOutput.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scm/DefaultBlameOutput.java new file mode 100644 index 00000000000..69d7d5f2481 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scm/DefaultBlameOutput.java @@ -0,0 +1,147 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scm; + +import com.google.common.base.Preconditions; +import java.text.Normalizer; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; +import javax.annotation.Nullable; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.scm.BlameCommand.BlameOutput; +import org.sonar.api.batch.scm.BlameLine; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.batch.index.BatchComponent; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.util.ProgressReport; +import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.scanner.protocol.output.ScannerReportWriter; +import org.sonar.scanner.protocol.output.ScannerReport.Changesets.Builder; + +class DefaultBlameOutput implements BlameOutput { + + private static final Logger LOG = Loggers.get(DefaultBlameOutput.class); + + private static final Pattern NON_ASCII_CHARS = Pattern.compile("[^\\x00-\\x7F]"); + private static final Pattern ACCENT_CODES = Pattern.compile("\\p{InCombiningDiacriticalMarks}+"); + + private final ScannerReportWriter writer; + private final BatchComponentCache componentCache; + private final Set<InputFile> allFilesToBlame = new HashSet<>(); + private ProgressReport progressReport; + private int count; + private int total; + + DefaultBlameOutput(ScannerReportWriter writer, BatchComponentCache componentCache, List<InputFile> filesToBlame) { + this.writer = writer; + this.componentCache = componentCache; + this.allFilesToBlame.addAll(filesToBlame); + count = 0; + total = filesToBlame.size(); + progressReport = new ProgressReport("Report about progress of SCM blame", TimeUnit.SECONDS.toMillis(10)); + progressReport.start(total + " files to be analyzed"); + } + + @Override + public synchronized void blameResult(InputFile file, List<BlameLine> lines) { + Preconditions.checkNotNull(file); + Preconditions.checkNotNull(lines); + Preconditions.checkArgument(allFilesToBlame.contains(file), "It was not expected to blame file %s", file.relativePath()); + + if (lines.size() != file.lines()) { + LOG.debug("Ignoring blame result since provider returned {} blame lines but file {} has {} lines", lines.size(), file.relativePath(), file.lines()); + return; + } + + BatchComponent batchComponent = componentCache.get(file); + Builder scmBuilder = ScannerReport.Changesets.newBuilder(); + scmBuilder.setComponentRef(batchComponent.batchId()); + Map<String, Integer> changesetsIdByRevision = new HashMap<>(); + + int lineId = 1; + for (BlameLine line : lines) { + validateLine(line, lineId, file); + Integer changesetId = changesetsIdByRevision.get(line.revision()); + if (changesetId == null) { + addChangeset(scmBuilder, line); + changesetId = scmBuilder.getChangesetCount() - 1; + changesetsIdByRevision.put(line.revision(), changesetId); + } + scmBuilder.addChangesetIndexByLine(changesetId); + lineId++; + } + writer.writeComponentChangesets(scmBuilder.build()); + allFilesToBlame.remove(file); + count++; + progressReport.message(count + "/" + total + " files analyzed"); + } + + private static void validateLine(BlameLine line, int lineId, InputFile file) { + Preconditions.checkArgument(StringUtils.isNotBlank(line.revision()), "Blame revision is blank for file %s at line %s", file.relativePath(), lineId); + Preconditions.checkArgument(line.date() != null, "Blame date is null for file %s at line %s", file.relativePath(), lineId); + } + + private static void addChangeset(Builder scmBuilder, BlameLine line) { + ScannerReport.Changesets.Changeset.Builder changesetBuilder = ScannerReport.Changesets.Changeset.newBuilder(); + changesetBuilder.setRevision(line.revision()); + changesetBuilder.setDate(line.date().getTime()); + if (StringUtils.isNotBlank(line.author())) { + changesetBuilder.setAuthor(normalizeString(line.author())); + } + + scmBuilder.addChangeset(changesetBuilder.build()); + } + + private static String normalizeString(@Nullable String inputString) { + if (inputString == null) { + return ""; + } + String lowerCasedString = inputString.toLowerCase(); + String stringWithoutAccents = removeAccents(lowerCasedString); + return removeNonAsciiCharacters(stringWithoutAccents); + } + + private static String removeAccents(String inputString) { + String unicodeDecomposedString = Normalizer.normalize(inputString, Normalizer.Form.NFD); + return ACCENT_CODES.matcher(unicodeDecomposedString).replaceAll(""); + } + + private static String removeNonAsciiCharacters(String inputString) { + return NON_ASCII_CHARS.matcher(inputString).replaceAll("_"); + } + + public void finish() { + progressReport.stop(count + "/" + total + " files analyzed"); + if (!allFilesToBlame.isEmpty()) { + LOG.warn("Missing blame information for the following files:"); + for (InputFile f : allFilesToBlame) { + LOG.warn(" * " + f.absolutePath()); + } + LOG.warn("This may lead to missing/broken features in SonarQube"); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scm/ScmConfiguration.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scm/ScmConfiguration.java new file mode 100644 index 00000000000..f6a88bf4465 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scm/ScmConfiguration.java @@ -0,0 +1,155 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scm; + +import com.google.common.base.Joiner; +import java.util.LinkedHashMap; +import java.util.Map; +import org.apache.commons.lang.StringUtils; +import org.picocontainer.Startable; +import org.sonar.api.CoreProperties; +import org.sonar.api.Properties; +import org.sonar.api.Property; +import org.sonar.api.PropertyType; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.InstantiationStrategy; +import org.sonar.api.batch.scm.ScmProvider; +import org.sonar.api.config.Settings; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.batch.scan.ImmutableProjectReactor; + +@Properties({ + @Property( + key = ScmConfiguration.FORCE_RELOAD_KEY, + defaultValue = "false", + name = "Force reloading of SCM information for all files", + description = "By default only files modified since previous analysis are inspected. Set this parameter to true to force the reloading.", + category = CoreProperties.CATEGORY_SCM, + project = false, + module = false, + global = false, + type = PropertyType.BOOLEAN) +}) +@InstantiationStrategy(InstantiationStrategy.PER_BATCH) +@BatchSide +public final class ScmConfiguration implements Startable { + private static final Logger LOG = Loggers.get(ScmConfiguration.class); + + public static final String FORCE_RELOAD_KEY = "sonar.scm.forceReloadAll"; + + private final ImmutableProjectReactor projectReactor; + private final Settings settings; + private final Map<String, ScmProvider> providerPerKey = new LinkedHashMap<>(); + private final AnalysisMode analysisMode; + + private ScmProvider provider; + + public ScmConfiguration(ImmutableProjectReactor projectReactor, AnalysisMode analysisMode, Settings settings, ScmProvider... providers) { + this.projectReactor = projectReactor; + this.analysisMode = analysisMode; + this.settings = settings; + for (ScmProvider scmProvider : providers) { + providerPerKey.put(scmProvider.key(), scmProvider); + } + } + + public ScmConfiguration(ImmutableProjectReactor projectReactor, AnalysisMode analysisMode, Settings settings) { + this(projectReactor, analysisMode, settings, new ScmProvider[0]); + } + + @Override + public void start() { + if (analysisMode.isIssues()) { + return; + } + if (isDisabled()) { + LOG.debug("SCM Step is disabled by configuration"); + return; + } + if (settings.hasKey(CoreProperties.SCM_PROVIDER_KEY)) { + String forcedProviderKey = settings.getString(CoreProperties.SCM_PROVIDER_KEY); + setProviderIfSupported(forcedProviderKey); + } else { + autodetection(); + if (this.provider == null) { + considerOldScmUrl(); + } + if (this.provider == null) { + LOG.warn("SCM provider autodetection failed. No SCM provider claims to support this project. Please use " + CoreProperties.SCM_PROVIDER_KEY + + " to define SCM of your project."); + } + } + } + + private void setProviderIfSupported(String forcedProviderKey) { + if (providerPerKey.containsKey(forcedProviderKey)) { + this.provider = providerPerKey.get(forcedProviderKey); + } else { + String supportedProviders = providerPerKey.isEmpty() ? "No SCM provider installed" : ("Supported SCM providers are " + Joiner.on(",").join(providerPerKey.keySet())); + throw new IllegalArgumentException("SCM provider was set to \"" + forcedProviderKey + "\" but no SCM provider found for this key. " + supportedProviders); + } + } + + private void considerOldScmUrl() { + if (settings.hasKey(CoreProperties.LINKS_SOURCES_DEV)) { + String url = settings.getString(CoreProperties.LINKS_SOURCES_DEV); + if (StringUtils.startsWith(url, "scm:")) { + String[] split = url.split(":"); + if (split.length > 1) { + setProviderIfSupported(split[1]); + } + } + } + + } + + private void autodetection() { + for (ScmProvider installedProvider : providerPerKey.values()) { + if (installedProvider.supports(projectReactor.getRoot().getBaseDir())) { + if (this.provider == null) { + this.provider = installedProvider; + } else { + throw new IllegalStateException("SCM provider autodetection failed. Both " + this.provider.key() + " and " + installedProvider.key() + + " claim to support this project. Please use " + CoreProperties.SCM_PROVIDER_KEY + " to define SCM of your project."); + } + } + } + } + + public ScmProvider provider() { + return provider; + } + + public boolean isDisabled() { + return settings.getBoolean(CoreProperties.SCM_DISABLED_KEY); + } + + public boolean forceReloadAll() { + return settings.getBoolean(FORCE_RELOAD_KEY); + } + + @Override + public void stop() { + // Nothing to do + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scm/ScmSensor.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scm/ScmSensor.java new file mode 100644 index 00000000000..078224bb6c6 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scm/ScmSensor.java @@ -0,0 +1,115 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scm; + +import java.util.LinkedList; +import java.util.List; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.InputFile.Status; +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.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.report.ReportPublisher; +import org.sonar.batch.repository.FileData; +import org.sonar.batch.repository.ProjectRepositories; + +public final class ScmSensor implements Sensor { + + private static final Logger LOG = Loggers.get(ScmSensor.class); + + private final ProjectDefinition projectDefinition; + private final ScmConfiguration configuration; + private final FileSystem fs; + private final ProjectRepositories projectRepositories; + private final BatchComponentCache resourceCache; + private final ReportPublisher publishReportJob; + + public ScmSensor(ProjectDefinition projectDefinition, ScmConfiguration configuration, + ProjectRepositories projectRepositories, FileSystem fs, BatchComponentCache resourceCache, ReportPublisher publishReportJob) { + this.projectDefinition = projectDefinition; + this.configuration = configuration; + this.projectRepositories = projectRepositories; + this.fs = fs; + this.resourceCache = resourceCache; + this.publishReportJob = publishReportJob; + } + + @Override + public void describe(SensorDescriptor descriptor) { + descriptor.name("SCM Sensor"); + } + + @Override + public void execute(SensorContext context) { + if (configuration.isDisabled()) { + LOG.info("SCM Publisher is disabled"); + return; + } + if (configuration.provider() == null) { + LOG.info("No SCM system was detected. You can use the '" + CoreProperties.SCM_PROVIDER_KEY + "' property to explicitly specify it."); + return; + } + + List<InputFile> filesToBlame = collectFilesToBlame(); + if (!filesToBlame.isEmpty()) { + String key = configuration.provider().key(); + LOG.info("SCM provider for this project is: " + key); + DefaultBlameOutput output = new DefaultBlameOutput(publishReportJob.getWriter(), resourceCache, filesToBlame); + try { + configuration.provider().blameCommand().blame(new DefaultBlameInput(fs, filesToBlame), output); + } finally { + output.finish(); + } + } + } + + private List<InputFile> collectFilesToBlame() { + if (configuration.forceReloadAll()) { + LOG.warn("Forced reloading of SCM data for all files."); + } + List<InputFile> filesToBlame = new LinkedList<>(); + for (InputFile f : fs.inputFiles(fs.predicates().all())) { + if (configuration.forceReloadAll() || f.status() != Status.SAME) { + addIfNotEmpty(filesToBlame, f); + } else { + // File status is SAME so that mean fileData exists + FileData fileData = projectRepositories.fileData(projectDefinition.getKeyWithBranch(), f.relativePath()); + if (StringUtils.isEmpty(fileData.revision())) { + addIfNotEmpty(filesToBlame, f); + } + } + } + return filesToBlame; + } + + private static void addIfNotEmpty(List<InputFile> filesToBlame, InputFile f) { + if (!f.isEmpty()) { + filesToBlame.add(f); + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/scm/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/scm/package-info.java new file mode 100644 index 00000000000..925761a7298 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/scm/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.scm; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/DefaultSensorContext.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/DefaultSensorContext.java new file mode 100644 index 00000000000..1080dab084a --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/DefaultSensorContext.java @@ -0,0 +1,115 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.sensor; + +import java.io.Serializable; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputModule; +import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.coverage.NewCoverage; +import org.sonar.api.batch.sensor.coverage.internal.DefaultCoverage; +import org.sonar.api.batch.sensor.cpd.NewCpdTokens; +import org.sonar.api.batch.sensor.cpd.internal.DefaultCpdTokens; +import org.sonar.api.batch.sensor.highlighting.NewHighlighting; +import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting; +import org.sonar.api.batch.sensor.internal.SensorStorage; +import org.sonar.api.batch.sensor.issue.NewIssue; +import org.sonar.api.batch.sensor.issue.internal.DefaultIssue; +import org.sonar.api.batch.sensor.measure.NewMeasure; +import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure; +import org.sonar.api.config.Settings; +import org.sonar.batch.sensor.noop.NoOpNewCpdTokens; +import org.sonar.batch.sensor.noop.NoOpNewHighlighting; + +public class DefaultSensorContext implements SensorContext { + + private static final NoOpNewHighlighting NO_OP_NEW_HIGHLIGHTING = new NoOpNewHighlighting(); + private static final NoOpNewCpdTokens NO_OP_NEW_CPD_TOKENS = new NoOpNewCpdTokens(); + + private final Settings settings; + private final FileSystem fs; + private final ActiveRules activeRules; + private final SensorStorage sensorStorage; + private final AnalysisMode analysisMode; + private final InputModule module; + + public DefaultSensorContext(InputModule module, Settings settings, FileSystem fs, ActiveRules activeRules, AnalysisMode analysisMode, SensorStorage sensorStorage) { + this.module = module; + this.settings = settings; + this.fs = fs; + this.activeRules = activeRules; + this.analysisMode = analysisMode; + this.sensorStorage = sensorStorage; + } + + @Override + public Settings settings() { + return settings; + } + + @Override + public FileSystem fileSystem() { + return fs; + } + + @Override + public ActiveRules activeRules() { + return activeRules; + } + + @Override + public InputModule module() { + return module; + } + + @Override + public <G extends Serializable> NewMeasure<G> newMeasure() { + return new DefaultMeasure<>(sensorStorage); + } + + @Override + public NewIssue newIssue() { + return new DefaultIssue(sensorStorage); + } + + @Override + public NewHighlighting newHighlighting() { + if (analysisMode.isIssues()) { + return NO_OP_NEW_HIGHLIGHTING; + } + return new DefaultHighlighting(sensorStorage); + } + + @Override + public NewCoverage newCoverage() { + return new DefaultCoverage(sensorStorage); + } + + @Override + public NewCpdTokens newCpdTokens() { + if (analysisMode.isIssues()) { + return NO_OP_NEW_CPD_TOKENS; + } + return new DefaultCpdTokens(sensorStorage); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/DefaultSensorStorage.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/DefaultSensorStorage.java new file mode 100644 index 00000000000..62e7d982d34 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/DefaultSensorStorage.java @@ -0,0 +1,285 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.sensor; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.collect.Iterables; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nonnull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputComponent; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.TextRange; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.measure.MetricFinder; +import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.batch.sensor.coverage.CoverageType; +import org.sonar.api.batch.sensor.coverage.internal.DefaultCoverage; +import org.sonar.api.batch.sensor.cpd.internal.DefaultCpdTokens; +import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting; +import org.sonar.api.batch.sensor.highlighting.internal.SyntaxHighlightingRule; +import org.sonar.api.batch.sensor.internal.SensorStorage; +import org.sonar.api.batch.sensor.issue.Issue; +import org.sonar.api.batch.sensor.measure.Measure; +import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure; +import org.sonar.api.config.Settings; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Metric; +import org.sonar.api.resources.File; +import org.sonar.api.resources.Resource; +import org.sonar.api.source.Symbol; +import org.sonar.api.utils.KeyValueFormat; +import org.sonar.api.utils.SonarException; +import org.sonar.batch.cpd.DefaultCpdBlockIndexer; +import org.sonar.batch.cpd.index.SonarCpdBlockIndex; +import org.sonar.batch.index.BatchComponent; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.issue.ModuleIssues; +import org.sonar.batch.report.ScannerReportUtils; +import org.sonar.batch.report.ReportPublisher; +import org.sonar.batch.scan.measure.MeasureCache; +import org.sonar.batch.sensor.coverage.CoverageExclusions; +import org.sonar.batch.source.DefaultSymbol; +import org.sonar.duplications.block.Block; +import org.sonar.duplications.internal.pmd.PmdBlockChunker; +import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.scanner.protocol.output.ScannerReportWriter; + +public class DefaultSensorStorage implements SensorStorage { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultSensorStorage.class); + + private static final List<Metric> INTERNAL_METRICS = Arrays.<Metric>asList( + // Computed by LinesSensor + CoreMetrics.LINES); + + private static final List<String> DEPRECATED_METRICS_KEYS = Arrays.<String>asList( + CoreMetrics.DEPENDENCY_MATRIX_KEY, + CoreMetrics.DIRECTORY_CYCLES_KEY, + CoreMetrics.DIRECTORY_EDGES_WEIGHT_KEY, + CoreMetrics.DIRECTORY_FEEDBACK_EDGES_KEY, + CoreMetrics.DIRECTORY_TANGLE_INDEX_KEY, + CoreMetrics.DIRECTORY_TANGLES_KEY, + CoreMetrics.FILE_CYCLES_KEY, + CoreMetrics.FILE_EDGES_WEIGHT_KEY, + CoreMetrics.FILE_FEEDBACK_EDGES_KEY, + CoreMetrics.FILE_TANGLE_INDEX_KEY, + CoreMetrics.FILE_TANGLES_KEY, + CoreMetrics.DUPLICATIONS_DATA_KEY); + + private final MetricFinder metricFinder; + private final ModuleIssues moduleIssues; + private final CoverageExclusions coverageExclusions; + private final BatchComponentCache componentCache; + private final ReportPublisher reportPublisher; + private final MeasureCache measureCache; + private final SonarCpdBlockIndex index; + private final Settings settings; + + public DefaultSensorStorage(MetricFinder metricFinder, ModuleIssues moduleIssues, + Settings settings, FileSystem fs, ActiveRules activeRules, + CoverageExclusions coverageExclusions, BatchComponentCache componentCache, ReportPublisher reportPublisher, MeasureCache measureCache, SonarCpdBlockIndex index) { + this.metricFinder = metricFinder; + this.moduleIssues = moduleIssues; + this.settings = settings; + this.coverageExclusions = coverageExclusions; + this.componentCache = componentCache; + this.reportPublisher = reportPublisher; + this.measureCache = measureCache; + this.index = index; + } + + private Metric findMetricOrFail(String metricKey) { + Metric m = (Metric) metricFinder.findByKey(metricKey); + if (m == null) { + throw new IllegalStateException("Unknow metric with key: " + metricKey); + } + return m; + } + + @Override + public void store(Measure newMeasure) { + DefaultMeasure<?> measure = (DefaultMeasure<?>) newMeasure; + org.sonar.api.measures.Metric m = findMetricOrFail(measure.metric().key()); + org.sonar.api.measures.Measure measureToSave = new org.sonar.api.measures.Measure(m); + setValueAccordingToMetricType(newMeasure, m, measureToSave); + measureToSave.setFromCore(measure.isFromCore()); + InputComponent inputComponent = newMeasure.inputComponent(); + Resource resource = componentCache.get(inputComponent).resource(); + if (coverageExclusions.accept(resource, measureToSave)) { + saveMeasure(resource, measureToSave); + } + } + + public org.sonar.api.measures.Measure saveMeasure(Resource resource, org.sonar.api.measures.Measure measure) { + if (DEPRECATED_METRICS_KEYS.contains(measure.getMetricKey())) { + // Ignore deprecated metrics + return null; + } + org.sonar.api.batch.measure.Metric metric = metricFinder.findByKey(measure.getMetricKey()); + if (metric == null) { + throw new SonarException("Unknown metric: " + measure.getMetricKey()); + } + if (!measure.isFromCore() && INTERNAL_METRICS.contains(metric)) { + LOG.debug("Metric " + metric.key() + " is an internal metric computed by SonarQube. Provided value is ignored."); + return measure; + } + if (measureCache.contains(resource, measure)) { + throw new SonarException("Can not add the same measure twice on " + resource + ": " + measure); + } + measureCache.put(resource, measure); + return measure; + } + + private void setValueAccordingToMetricType(Measure<?> measure, org.sonar.api.measures.Metric<?> m, org.sonar.api.measures.Measure measureToSave) { + switch (m.getType()) { + case BOOL: + measureToSave.setValue(Boolean.TRUE.equals(measure.value()) ? 1.0 : 0.0); + break; + case INT: + case MILLISEC: + case WORK_DUR: + case FLOAT: + case PERCENT: + case RATING: + measureToSave.setValue(((Number) measure.value()).doubleValue()); + break; + case STRING: + case LEVEL: + case DATA: + case DISTRIB: + measureToSave.setData((String) measure.value()); + break; + default: + throw new UnsupportedOperationException("Unsupported type :" + m.getType()); + } + } + + @Override + public void store(Issue issue) { + moduleIssues.initAndAddIssue(issue); + } + + private File getFile(InputFile file) { + BatchComponent r = componentCache.get(file); + if (r == null) { + throw new IllegalStateException("Provided input file is not indexed"); + } + return (File) r.resource(); + } + + @Override + public void store(DefaultHighlighting highlighting) { + ScannerReportWriter writer = reportPublisher.getWriter(); + DefaultInputFile inputFile = (DefaultInputFile) highlighting.inputFile(); + writer.writeComponentSyntaxHighlighting(componentCache.get(inputFile).batchId(), + Iterables.transform(highlighting.getSyntaxHighlightingRuleSet(), new BuildSyntaxHighlighting())); + } + + public void store(DefaultInputFile inputFile, Map<Symbol, Set<TextRange>> referencesBySymbol) { + ScannerReportWriter writer = reportPublisher.getWriter(); + writer.writeComponentSymbols(componentCache.get(inputFile).batchId(), + Iterables.transform(referencesBySymbol.entrySet(), new Function<Map.Entry<Symbol, Set<TextRange>>, ScannerReport.Symbol>() { + private ScannerReport.Symbol.Builder builder = ScannerReport.Symbol.newBuilder(); + private ScannerReport.TextRange.Builder rangeBuilder = ScannerReport.TextRange.newBuilder(); + + @Override + public ScannerReport.Symbol apply(Map.Entry<Symbol, Set<TextRange>> input) { + builder.clear(); + rangeBuilder.clear(); + DefaultSymbol symbol = (DefaultSymbol) input.getKey(); + builder.setDeclaration(rangeBuilder.setStartLine(symbol.range().start().line()) + .setStartOffset(symbol.range().start().lineOffset()) + .setEndLine(symbol.range().end().line()) + .setEndOffset(symbol.range().end().lineOffset()) + .build()); + for (TextRange reference : input.getValue()) { + builder.addReference(rangeBuilder.setStartLine(reference.start().line()) + .setStartOffset(reference.start().lineOffset()) + .setEndLine(reference.end().line()) + .setEndOffset(reference.end().lineOffset()) + .build()); + } + return builder.build(); + } + + })); + } + + @Override + public void store(DefaultCoverage defaultCoverage) { + File file = getFile(defaultCoverage.inputFile()); + if (coverageExclusions.hasMatchingPattern(file)) { + return; + } + CoverageType type = defaultCoverage.type(); + if (defaultCoverage.linesToCover() > 0) { + saveMeasure(file, new org.sonar.api.measures.Measure(type.linesToCover(), (double) defaultCoverage.linesToCover())); + saveMeasure(file, new org.sonar.api.measures.Measure(type.uncoveredLines(), (double) (defaultCoverage.linesToCover() - defaultCoverage.coveredLines()))); + saveMeasure(file, new org.sonar.api.measures.Measure(type.lineHitsData()).setData(KeyValueFormat.format(defaultCoverage.hitsByLine()))); + } + if (defaultCoverage.conditions() > 0) { + saveMeasure(file, new org.sonar.api.measures.Measure(type.conditionsToCover(), (double) defaultCoverage.conditions())); + saveMeasure(file, new org.sonar.api.measures.Measure(type.uncoveredConditions(), (double) (defaultCoverage.conditions() - defaultCoverage.coveredConditions()))); + saveMeasure(file, new org.sonar.api.measures.Measure(type.coveredConditionsByLine()).setData(KeyValueFormat.format(defaultCoverage.coveredConditionsByLine()))); + saveMeasure(file, new org.sonar.api.measures.Measure(type.conditionsByLine()).setData(KeyValueFormat.format(defaultCoverage.conditionsByLine()))); + } + } + + private static class BuildSyntaxHighlighting implements Function<SyntaxHighlightingRule, ScannerReport.SyntaxHighlighting> { + private ScannerReport.SyntaxHighlighting.Builder builder = ScannerReport.SyntaxHighlighting.newBuilder(); + private ScannerReport.TextRange.Builder rangeBuilder = ScannerReport.TextRange.newBuilder(); + + @Override + public ScannerReport.SyntaxHighlighting apply(@Nonnull SyntaxHighlightingRule input) { + builder.setRange(rangeBuilder.setStartLine(input.range().start().line()) + .setStartOffset(input.range().start().lineOffset()) + .setEndLine(input.range().end().line()) + .setEndOffset(input.range().end().lineOffset()) + .build()); + builder.setType(ScannerReportUtils.toProtocolType(input.getTextType())); + return builder.build(); + } + } + + @Override + public void store(DefaultCpdTokens defaultCpdTokens) { + InputFile inputFile = defaultCpdTokens.inputFile(); + PmdBlockChunker blockChunker = new PmdBlockChunker(getBlockSize(inputFile.language())); + List<Block> blocks = blockChunker.chunk(inputFile.key(), defaultCpdTokens.getTokenLines()); + index.insert(inputFile, blocks); + } + + @VisibleForTesting + int getBlockSize(String languageKey) { + int blockSize = settings.getInt("sonar.cpd." + languageKey + ".minimumLines"); + if (blockSize == 0) { + blockSize = DefaultCpdBlockIndexer.getDefaultBlockSize(languageKey); + } + return blockSize; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/SensorOptimizer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/SensorOptimizer.java new file mode 100644 index 00000000000..7e8dd63e93a --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/SensorOptimizer.java @@ -0,0 +1,98 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.sensor; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.fs.FilePredicate; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; +import org.sonar.api.config.Settings; + +@BatchSide +public class SensorOptimizer { + + private static final Logger LOG = LoggerFactory.getLogger(SensorOptimizer.class); + + private final FileSystem fs; + private final ActiveRules activeRules; + private final Settings settings; + + public SensorOptimizer(FileSystem fs, ActiveRules activeRules, Settings settings) { + this.fs = fs; + this.activeRules = activeRules; + this.settings = settings; + } + + /** + * Decide if the given Sensor should be executed. + */ + public boolean shouldExecute(DefaultSensorDescriptor descriptor) { + if (!fsCondition(descriptor)) { + LOG.debug("'{}' skipped because there is no related file in current project", descriptor.name()); + return false; + } + if (!activeRulesCondition(descriptor)) { + LOG.debug("'{}' skipped because there is no related rule activated in the quality profile", descriptor.name()); + return false; + } + if (!settingsCondition(descriptor)) { + LOG.debug("'{}' skipped because one of the required properties is missing", descriptor.name()); + return false; + } + return true; + } + + private boolean settingsCondition(DefaultSensorDescriptor descriptor) { + if (!descriptor.properties().isEmpty()) { + for (String propertyKey : descriptor.properties()) { + if (!settings.hasKey(propertyKey)) { + return false; + } + } + } + return true; + } + + private boolean activeRulesCondition(DefaultSensorDescriptor descriptor) { + if (!descriptor.ruleRepositories().isEmpty()) { + for (String repoKey : descriptor.ruleRepositories()) { + if (!activeRules.findByRepository(repoKey).isEmpty()) { + return true; + } + } + return false; + } + return true; + } + + private boolean fsCondition(DefaultSensorDescriptor descriptor) { + if (!descriptor.languages().isEmpty() || descriptor.type() != null) { + FilePredicate langPredicate = descriptor.languages().isEmpty() ? fs.predicates().all() : fs.predicates().hasLanguages(descriptor.languages()); + + FilePredicate typePredicate = descriptor.type() == null ? fs.predicates().all() : fs.predicates().hasType(descriptor.type()); + return fs.hasFiles(fs.predicates().and(langPredicate, typePredicate)); + } + return true; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/SensorWrapper.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/SensorWrapper.java new file mode 100644 index 00000000000..3e82a222546 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/SensorWrapper.java @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.sensor; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.sensor.Sensor; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; +import org.sonar.api.resources.Project; + +public class SensorWrapper implements org.sonar.api.batch.Sensor { + + private static final Logger LOG = LoggerFactory.getLogger(SensorWrapper.class); + + private Sensor wrappedSensor; + private SensorContext adaptor; + private DefaultSensorDescriptor descriptor; + private SensorOptimizer optimizer; + + public SensorWrapper(Sensor newSensor, SensorContext adaptor, SensorOptimizer optimizer) { + this.wrappedSensor = newSensor; + this.optimizer = optimizer; + descriptor = new DefaultSensorDescriptor(); + newSensor.describe(descriptor); + this.adaptor = adaptor; + } + + public Sensor wrappedSensor() { + return wrappedSensor; + } + + @Override + public boolean shouldExecuteOnProject(Project project) { + return optimizer.shouldExecute(descriptor); + } + + @Override + public void analyse(Project module, org.sonar.api.batch.SensorContext context) { + wrappedSensor.execute(adaptor); + } + + @Override + public String toString() { + return descriptor.name() + (LOG.isDebugEnabled() ? " (wrapped)" : ""); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/coverage/CoverageConstants.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/coverage/CoverageConstants.java new file mode 100644 index 00000000000..569ac157e42 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/coverage/CoverageConstants.java @@ -0,0 +1,42 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.sensor.coverage; + +import com.google.common.collect.ImmutableList; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Metric; + +import java.util.Collection; + +public class CoverageConstants { + + public static final Collection<Metric> COVERAGE_METRICS = ImmutableList.<Metric>of(CoreMetrics.LINES_TO_COVER, CoreMetrics.UNCOVERED_LINES, CoreMetrics.NEW_LINES_TO_COVER, + CoreMetrics.NEW_UNCOVERED_LINES, CoreMetrics.CONDITIONS_TO_COVER, CoreMetrics.UNCOVERED_CONDITIONS, + CoreMetrics.NEW_CONDITIONS_TO_COVER, CoreMetrics.NEW_UNCOVERED_CONDITIONS); + + public static final Collection<Metric> LINE_COVERAGE_METRICS = ImmutableList.<Metric>of(CoreMetrics.UNCOVERED_LINES, CoreMetrics.LINES_TO_COVER, CoreMetrics.NEW_UNCOVERED_LINES, + CoreMetrics.NEW_LINES_TO_COVER); + + public static final Collection<Metric> BRANCH_COVERAGE_METRICS = ImmutableList.<Metric>of(CoreMetrics.UNCOVERED_CONDITIONS, CoreMetrics.CONDITIONS_TO_COVER, + CoreMetrics.NEW_UNCOVERED_CONDITIONS, CoreMetrics.NEW_CONDITIONS_TO_COVER); + + private CoverageConstants() { + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/coverage/CoverageExclusions.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/coverage/CoverageExclusions.java new file mode 100644 index 00000000000..198b61141bb --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/coverage/CoverageExclusions.java @@ -0,0 +1,210 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.sensor.coverage; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableList.Builder; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import javax.annotation.CheckForNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.config.Settings; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.Metric; +import org.sonar.api.resources.Resource; +import org.sonar.api.utils.KeyValueFormat; +import org.sonar.api.utils.WildcardPattern; + +public class CoverageExclusions { + + private static final Logger LOG = LoggerFactory.getLogger(CoverageExclusions.class); + + private final Settings settings; + private final Set<Metric> coverageMetrics; + private final Set<Metric> byLineMetrics; + private Collection<WildcardPattern> resourcePatterns; + + private final FileSystem fs; + + public CoverageExclusions(Settings settings, FileSystem fs) { + this.settings = settings; + this.fs = fs; + this.coverageMetrics = new HashSet<>(); + this.byLineMetrics = new HashSet<>(); + // UT + coverageMetrics.add(CoreMetrics.COVERAGE); + coverageMetrics.add(CoreMetrics.LINE_COVERAGE); + coverageMetrics.add(CoreMetrics.BRANCH_COVERAGE); + coverageMetrics.add(CoreMetrics.UNCOVERED_LINES); + coverageMetrics.add(CoreMetrics.LINES_TO_COVER); + coverageMetrics.add(CoreMetrics.UNCOVERED_CONDITIONS); + coverageMetrics.add(CoreMetrics.CONDITIONS_TO_COVER); + coverageMetrics.add(CoreMetrics.CONDITIONS_BY_LINE); + coverageMetrics.add(CoreMetrics.COVERED_CONDITIONS_BY_LINE); + coverageMetrics.add(CoreMetrics.COVERAGE_LINE_HITS_DATA); + coverageMetrics.add(CoreMetrics.NEW_LINES_TO_COVER); + coverageMetrics.add(CoreMetrics.NEW_UNCOVERED_LINES); + coverageMetrics.add(CoreMetrics.NEW_UNCOVERED_CONDITIONS); + // IT + coverageMetrics.add(CoreMetrics.IT_COVERAGE); + coverageMetrics.add(CoreMetrics.IT_LINE_COVERAGE); + coverageMetrics.add(CoreMetrics.IT_BRANCH_COVERAGE); + coverageMetrics.add(CoreMetrics.IT_UNCOVERED_LINES); + coverageMetrics.add(CoreMetrics.IT_LINES_TO_COVER); + coverageMetrics.add(CoreMetrics.IT_UNCOVERED_CONDITIONS); + coverageMetrics.add(CoreMetrics.IT_CONDITIONS_TO_COVER); + coverageMetrics.add(CoreMetrics.IT_CONDITIONS_BY_LINE); + coverageMetrics.add(CoreMetrics.IT_COVERED_CONDITIONS_BY_LINE); + coverageMetrics.add(CoreMetrics.IT_COVERAGE_LINE_HITS_DATA); + coverageMetrics.add(CoreMetrics.NEW_IT_LINES_TO_COVER); + coverageMetrics.add(CoreMetrics.NEW_IT_UNCOVERED_LINES); + coverageMetrics.add(CoreMetrics.NEW_IT_UNCOVERED_CONDITIONS); + // OVERALL + coverageMetrics.add(CoreMetrics.OVERALL_COVERAGE); + coverageMetrics.add(CoreMetrics.OVERALL_LINE_COVERAGE); + coverageMetrics.add(CoreMetrics.OVERALL_BRANCH_COVERAGE); + coverageMetrics.add(CoreMetrics.OVERALL_UNCOVERED_LINES); + coverageMetrics.add(CoreMetrics.OVERALL_LINES_TO_COVER); + coverageMetrics.add(CoreMetrics.OVERALL_UNCOVERED_CONDITIONS); + coverageMetrics.add(CoreMetrics.OVERALL_CONDITIONS_TO_COVER); + coverageMetrics.add(CoreMetrics.OVERALL_CONDITIONS_BY_LINE); + coverageMetrics.add(CoreMetrics.OVERALL_COVERED_CONDITIONS_BY_LINE); + coverageMetrics.add(CoreMetrics.OVERALL_COVERAGE_LINE_HITS_DATA); + coverageMetrics.add(CoreMetrics.NEW_OVERALL_LINES_TO_COVER); + coverageMetrics.add(CoreMetrics.NEW_OVERALL_UNCOVERED_LINES); + coverageMetrics.add(CoreMetrics.NEW_OVERALL_UNCOVERED_CONDITIONS); + + byLineMetrics.add(CoreMetrics.OVERALL_COVERAGE_LINE_HITS_DATA); + byLineMetrics.add(CoreMetrics.OVERALL_CONDITIONS_BY_LINE); + byLineMetrics.add(CoreMetrics.OVERALL_COVERED_CONDITIONS_BY_LINE); + byLineMetrics.add(CoreMetrics.COVERAGE_LINE_HITS_DATA); + byLineMetrics.add(CoreMetrics.COVERED_CONDITIONS_BY_LINE); + byLineMetrics.add(CoreMetrics.CONDITIONS_BY_LINE); + byLineMetrics.add(CoreMetrics.IT_COVERAGE_LINE_HITS_DATA); + byLineMetrics.add(CoreMetrics.IT_CONDITIONS_BY_LINE); + byLineMetrics.add(CoreMetrics.IT_COVERED_CONDITIONS_BY_LINE); + + initPatterns(); + } + + private boolean isLineMetrics(Metric<?> metric) { + return this.byLineMetrics.contains(metric); + } + + public void validate(Measure<?> measure, InputFile inputFile) { + Metric<?> metric = measure.getMetric(); + + if (!isLineMetrics(metric)) { + return; + } + + Map<Integer, Integer> m = KeyValueFormat.parseIntInt(measure.getData()); + validatePositiveLine(m, inputFile.absolutePath()); + validateMaxLine(m, inputFile); + } + + @CheckForNull + private InputFile getInputFile(String filePath) { + return fs.inputFile(fs.predicates().hasRelativePath(filePath)); + } + + public void validate(Measure<?> measure, String filePath) { + Metric<?> metric = measure.getMetric(); + + if (!isLineMetrics(metric)) { + return; + } + + InputFile inputFile = getInputFile(filePath); + + if (inputFile == null) { + throw new IllegalStateException(String.format("Can't create measure for resource '%s': resource is not indexed as a file", filePath)); + } + + validate(measure, inputFile); + } + + private static void validateMaxLine(Map<Integer, Integer> m, InputFile inputFile) { + int maxLine = inputFile.lines(); + + for (int l : m.keySet()) { + if (l > maxLine) { + throw new IllegalStateException(String.format("Can't create measure for line %d for file '%s' with %d lines", l, inputFile.absolutePath(), maxLine)); + } + } + } + + private static void validatePositiveLine(Map<Integer, Integer> m, String filePath) { + for (int l : m.keySet()) { + if (l <= 0) { + throw new IllegalStateException(String.format("Measure with line %d for file '%s' must be > 0", l, filePath)); + } + } + } + + public boolean accept(Resource resource, Measure<?> measure) { + if (isCoverageMetric(measure.getMetric())) { + return !hasMatchingPattern(resource); + } else { + return true; + } + } + + private boolean isCoverageMetric(Metric<?> metric) { + return this.coverageMetrics.contains(metric); + } + + public boolean hasMatchingPattern(Resource resource) { + boolean found = false; + Iterator<WildcardPattern> iterator = resourcePatterns.iterator(); + while (!found && iterator.hasNext()) { + found = resource.matchFilePattern(iterator.next().toString()); + } + return found; + } + + @VisibleForTesting + final void initPatterns() { + Builder<WildcardPattern> builder = ImmutableList.builder(); + for (String pattern : settings.getStringArray(CoreProperties.PROJECT_COVERAGE_EXCLUSIONS_PROPERTY)) { + builder.add(WildcardPattern.create(pattern)); + } + resourcePatterns = builder.build(); + log("Excluded sources for coverage: ", resourcePatterns); + } + + private static void log(String title, Collection<WildcardPattern> patterns) { + if (!patterns.isEmpty()) { + LOG.info(title); + for (WildcardPattern pattern : patterns) { + LOG.info(" " + pattern); + } + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/coverage/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/coverage/package-info.java new file mode 100644 index 00000000000..b4c6be3d144 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/coverage/package-info.java @@ -0,0 +1,21 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@javax.annotation.ParametersAreNonnullByDefault +package org.sonar.batch.sensor.coverage; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/noop/NoOpNewCpdTokens.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/noop/NoOpNewCpdTokens.java new file mode 100644 index 00000000000..afd00476b2a --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/noop/NoOpNewCpdTokens.java @@ -0,0 +1,43 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.sensor.noop; + +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.TextRange; +import org.sonar.api.batch.sensor.cpd.NewCpdTokens; + +public class NoOpNewCpdTokens implements NewCpdTokens { + @Override + public void save() { + // Do nothing + } + + @Override + public NoOpNewCpdTokens onFile(InputFile inputFile) { + // Do nothing + return this; + } + + @Override + public NewCpdTokens addToken(TextRange range, String image) { + // Do nothing + return this; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/noop/NoOpNewHighlighting.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/noop/NoOpNewHighlighting.java new file mode 100644 index 00000000000..7ead32f7f04 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/noop/NoOpNewHighlighting.java @@ -0,0 +1,43 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.sensor.noop; + +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.sensor.highlighting.NewHighlighting; +import org.sonar.api.batch.sensor.highlighting.TypeOfText; + +public class NoOpNewHighlighting implements NewHighlighting { + @Override + public void save() { + // Do nothing + } + + @Override + public NewHighlighting onFile(InputFile inputFile) { + // Do nothing + return this; + } + + @Override + public NewHighlighting highlight(int startOffset, int endOffset, TypeOfText typeOfText) { + // Do nothing + return this; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/noop/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/noop/package-info.java new file mode 100644 index 00000000000..0a654a40800 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/noop/package-info.java @@ -0,0 +1,21 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@javax.annotation.ParametersAreNonnullByDefault +package org.sonar.batch.sensor.noop; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/package-info.java new file mode 100644 index 00000000000..4c9f457a97f --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/package-info.java @@ -0,0 +1,21 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@javax.annotation.ParametersAreNonnullByDefault +package org.sonar.batch.sensor; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/source/CodeColorizerSensor.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/CodeColorizerSensor.java new file mode 100644 index 00000000000..616ac48ffef --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/CodeColorizerSensor.java @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.source; + +import org.sonar.api.batch.Phase; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.sensor.Sensor; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.SensorDescriptor; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.report.ReportPublisher; +import org.sonar.scanner.protocol.output.ScannerReportReader; + +@Phase(name = Phase.Name.POST) +public final class CodeColorizerSensor implements Sensor { + + private final ReportPublisher reportPublisher; + private final BatchComponentCache resourceCache; + private final CodeColorizers codeColorizers; + + public CodeColorizerSensor(ReportPublisher reportPublisher, BatchComponentCache resourceCache, CodeColorizers codeColorizers) { + this.reportPublisher = reportPublisher; + this.resourceCache = resourceCache; + this.codeColorizers = codeColorizers; + } + + @Override + public void describe(SensorDescriptor descriptor) { + descriptor.name("Code Colorizer Sensor"); + } + + @Override + public void execute(final SensorContext context) { + FileSystem fs = context.fileSystem(); + for (InputFile f : fs.inputFiles(fs.predicates().all())) { + ScannerReportReader reader = new ScannerReportReader(reportPublisher.getReportDir()); + int batchId = resourceCache.get(f).batchId(); + String language = f.language(); + if (reader.hasSyntaxHighlighting(batchId) || language == null) { + continue; + } + codeColorizers.toSyntaxHighlighting(f.file(), fs.encoding(), language, context.newHighlighting().onFile(f)); + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/source/CodeColorizers.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/CodeColorizers.java new file mode 100644 index 00000000000..39a4a0b1c98 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/CodeColorizers.java @@ -0,0 +1,91 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.source; + +import com.google.common.collect.Lists; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.CheckForNull; +import org.apache.commons.io.input.BOMInputStream; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.sensor.highlighting.NewHighlighting; +import org.sonar.api.web.CodeColorizerFormat; +import org.sonar.colorizer.JavaTokenizers; +import org.sonar.colorizer.Tokenizer; + +/** + * Central point for sonar-colorizer extensions + */ +@BatchSide +public class CodeColorizers { + + private static final Logger LOG = LoggerFactory.getLogger(CodeColorizers.class); + + private final Map<String, CodeColorizerFormat> byLang; + + public CodeColorizers(List<CodeColorizerFormat> formats) { + byLang = new HashMap<>(); + for (CodeColorizerFormat format : formats) { + byLang.put(format.getLanguageKey(), format); + } + + LOG.debug("Code colorizer, supported languages: " + StringUtils.join(byLang.keySet(), ",")); + } + + /** + * Used when no plugin is defining some CodeColorizerFormat + */ + public CodeColorizers() { + this(Lists.<CodeColorizerFormat>newArrayList()); + } + + @CheckForNull + public void toSyntaxHighlighting(File file, Charset charset, String language, NewHighlighting highlighting) { + CodeColorizerFormat format = byLang.get(language); + List<Tokenizer> tokenizers; + if (format == null) { + // Workaround for Java test code since Java plugin only provides highlighting for main source and no colorizer + // TODO can be dropped when Java plugin embed its own CodeColorizerFormat of (better) provides highlighting for tests + // See SONARJAVA-830 + if ("java".equals(language)) { + tokenizers = JavaTokenizers.forHtml(); + } else { + return; + } + } else { + tokenizers = format.getTokenizers(); + } + try (Reader reader = new BufferedReader(new InputStreamReader(new BOMInputStream(new FileInputStream(file)), charset))) { + new HighlightingRenderer().render(reader, tokenizers, highlighting); + } catch (Exception e) { + LOG.warn("Unable to perform colorization of file " + file, e); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/source/DefaultHighlightable.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/DefaultHighlightable.java new file mode 100644 index 00000000000..80403efc298 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/DefaultHighlightable.java @@ -0,0 +1,88 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.source; + +import org.sonar.api.batch.AnalysisMode; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.sensor.highlighting.TypeOfText; +import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting; +import org.sonar.api.batch.sensor.internal.SensorStorage; +import org.sonar.api.source.Highlightable; + +/** + * @since 3.6 + */ +public class DefaultHighlightable implements Highlightable { + + private static final HighlightingBuilder NO_OP_BUILDER = new NoOpHighlightingBuilder(); + private final DefaultInputFile inputFile; + private final SensorStorage sensorStorage; + private final AnalysisMode analysisMode; + + public DefaultHighlightable(DefaultInputFile inputFile, SensorStorage sensorStorage, AnalysisMode analysisMode) { + this.inputFile = inputFile; + this.sensorStorage = sensorStorage; + this.analysisMode = analysisMode; + } + + @Override + public HighlightingBuilder newHighlighting() { + if (analysisMode.isIssues()) { + return NO_OP_BUILDER; + } + DefaultHighlighting defaultHighlighting = new DefaultHighlighting(sensorStorage); + defaultHighlighting.onFile(inputFile); + return new DefaultHighlightingBuilder(defaultHighlighting); + } + + private static final class NoOpHighlightingBuilder implements HighlightingBuilder { + @Override + public HighlightingBuilder highlight(int startOffset, int endOffset, String typeOfText) { + // Do nothing + return this; + } + + @Override + public void done() { + // Do nothing + } + } + + private static class DefaultHighlightingBuilder implements HighlightingBuilder { + + private final DefaultHighlighting defaultHighlighting; + + public DefaultHighlightingBuilder(DefaultHighlighting defaultHighlighting) { + this.defaultHighlighting = defaultHighlighting; + } + + @Override + public HighlightingBuilder highlight(int startOffset, int endOffset, String typeOfText) { + TypeOfText type = org.sonar.api.batch.sensor.highlighting.TypeOfText.forCssClass(typeOfText); + defaultHighlighting.highlight(startOffset, endOffset, type); + return this; + } + + @Override + public void done() { + defaultHighlighting.save(); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/source/DefaultSymbol.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/DefaultSymbol.java new file mode 100644 index 00000000000..a317bfb2de8 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/DefaultSymbol.java @@ -0,0 +1,66 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.source; + +import com.google.common.base.Objects; +import org.sonar.api.batch.fs.TextRange; + +import java.io.Serializable; + +public class DefaultSymbol implements org.sonar.api.source.Symbol, Serializable { + + private TextRange range; + private int length; + + public DefaultSymbol(TextRange range, int length) { + this.range = range; + this.length = length; + } + + @Override + public int getDeclarationStartOffset() { + throw new UnsupportedOperationException("getDeclarationStartOffset"); + } + + @Override + public int getDeclarationEndOffset() { + throw new UnsupportedOperationException("getDeclarationEndOffset"); + } + + @Override + public String getFullyQualifiedName() { + throw new UnsupportedOperationException("getFullyQualifiedName"); + } + + public TextRange range() { + return range; + } + + public int getLength() { + return length; + } + + @Override + public String toString() { + return Objects.toStringHelper("Symbol") + .add("range", range) + .toString(); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/source/DefaultSymbolTable.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/DefaultSymbolTable.java new file mode 100644 index 00000000000..f4ecc8e3761 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/DefaultSymbolTable.java @@ -0,0 +1,123 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.source; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import org.sonar.api.batch.fs.TextRange; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.source.Symbol; +import org.sonar.api.source.Symbolizable; + +public class DefaultSymbolTable implements Symbolizable.SymbolTable { + + private Map<Symbol, Set<TextRange>> referencesBySymbol; + + private DefaultSymbolTable(Map<Symbol, Set<TextRange>> referencesBySymbol) { + this.referencesBySymbol = referencesBySymbol; + } + + public Map<Symbol, Set<TextRange>> getReferencesBySymbol() { + return referencesBySymbol; + } + + @Override + public List<Symbol> symbols() { + List<Symbol> result = new ArrayList<>(); + for (Symbol symbol : referencesBySymbol.keySet()) { + result.add((Symbol) symbol); + } + return result; + } + + @Override + public List<Integer> references(Symbol symbol) { + throw new UnsupportedOperationException("references"); + } + + public static class Builder implements Symbolizable.SymbolTableBuilder { + + private static final class FakeSymbol implements Symbol { + @Override + public String getFullyQualifiedName() { + return null; + } + + @Override + public int getDeclarationStartOffset() { + return 0; + } + + @Override + public int getDeclarationEndOffset() { + return 0; + } + } + + private final Map<Symbol, Set<TextRange>> referencesBySymbol = new LinkedHashMap<>(); + private final DefaultInputFile inputFile; + + public Builder(DefaultInputFile inputFile) { + this.inputFile = inputFile; + } + + @Override + public Symbol newSymbol(int fromOffset, int toOffset) { + TextRange declarationRange = inputFile.newRange(fromOffset, toOffset); + DefaultSymbol symbol = new DefaultSymbol(declarationRange, toOffset - fromOffset); + referencesBySymbol.put(symbol, new TreeSet<>(new Comparator<TextRange>() { + @Override + public int compare(TextRange o1, TextRange o2) { + return o1.start().compareTo(o2.start()); + } + })); + return symbol; + } + + @Override + public void newReference(Symbol symbol, int fromOffset) { + newReference(symbol, fromOffset, fromOffset + ((DefaultSymbol) symbol).getLength()); + } + + @Override + public void newReference(Symbol symbol, int fromOffset, int toOffset) { + if (!referencesBySymbol.containsKey(symbol)) { + throw new UnsupportedOperationException("Cannot add reference to a symbol in another file"); + } + TextRange referenceRange = inputFile.newRange(fromOffset, toOffset); + + if (referenceRange.overlap(((DefaultSymbol) symbol).range())) { + throw new UnsupportedOperationException("Cannot add reference (" + fromOffset + ") overlapping " + symbol + " in " + inputFile.key()); + } + referencesBySymbol.get(symbol).add(referenceRange); + } + + @Override + public Symbolizable.SymbolTable build() { + return new DefaultSymbolTable(referencesBySymbol); + } + + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/source/DefaultSymbolizable.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/DefaultSymbolizable.java new file mode 100644 index 00000000000..cddeabb9683 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/DefaultSymbolizable.java @@ -0,0 +1,113 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.source; + +import java.util.Collections; +import java.util.List; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.source.Symbol; +import org.sonar.api.source.Symbolizable; +import org.sonar.batch.sensor.DefaultSensorStorage; + +public class DefaultSymbolizable implements Symbolizable { + + private static final NoOpSymbolTableBuilder NO_OP_SYMBOL_TABLE_BUILDER = new NoOpSymbolTableBuilder(); + private static final NoOpSymbolTable NO_OP_SYMBOL_TABLE = new NoOpSymbolTable(); + private static final NoOpSymbol NO_OP_SYMBOL = new NoOpSymbol(); + + private static final class NoOpSymbolTableBuilder implements SymbolTableBuilder { + @Override + public Symbol newSymbol(int fromOffset, int toOffset) { + return NO_OP_SYMBOL; + } + + @Override + public void newReference(Symbol symbol, int fromOffset) { + // Do nothing + } + + @Override + public void newReference(Symbol symbol, int fromOffset, int toOffset) { + // Do nothing + } + + @Override + public SymbolTable build() { + return NO_OP_SYMBOL_TABLE; + } + } + + private static final class NoOpSymbolTable implements SymbolTable { + @Override + public List<Symbol> symbols() { + return Collections.emptyList(); + } + + @Override + public List<Integer> references(Symbol symbol) { + return Collections.emptyList(); + } + } + + private static final class NoOpSymbol implements Symbol { + @Override + public String getFullyQualifiedName() { + return null; + } + + @Override + public int getDeclarationStartOffset() { + return 0; + } + + @Override + public int getDeclarationEndOffset() { + return 0; + } + } + + private final DefaultInputFile inputFile; + private final DefaultSensorStorage sensorStorage; + private final AnalysisMode analysisMode; + + public DefaultSymbolizable(DefaultInputFile inputFile, DefaultSensorStorage sensorStorage, AnalysisMode analysisMode) { + this.inputFile = inputFile; + this.sensorStorage = sensorStorage; + this.analysisMode = analysisMode; + } + + @Override + public SymbolTableBuilder newSymbolTableBuilder() { + if (analysisMode.isIssues()) { + return NO_OP_SYMBOL_TABLE_BUILDER; + } + return new DefaultSymbolTable.Builder(inputFile); + } + + @Override + public void setSymbolTable(SymbolTable symbolTable) { + if (analysisMode.isIssues()) { + // No need for symbols in issues mode + return; + } + sensorStorage.store(inputFile, ((DefaultSymbolTable) symbolTable).getReferencesBySymbol()); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/source/HighlightableBuilder.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/HighlightableBuilder.java new file mode 100644 index 00000000000..d911516b984 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/HighlightableBuilder.java @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.source; + +import javax.annotation.CheckForNull; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.sensor.internal.SensorStorage; +import org.sonar.api.source.Highlightable; +import org.sonar.batch.deprecated.perspectives.PerspectiveBuilder; +import org.sonar.batch.index.BatchComponent; + +public class HighlightableBuilder extends PerspectiveBuilder<Highlightable> { + + private final SensorStorage sensorStorage; + private final AnalysisMode analysisMode; + + public HighlightableBuilder(SensorStorage sensorStorage, AnalysisMode analysisMode) { + super(Highlightable.class); + this.sensorStorage = sensorStorage; + this.analysisMode = analysisMode; + } + + @CheckForNull + @Override + public Highlightable loadPerspective(Class<Highlightable> perspectiveClass, BatchComponent component) { + if (component.isFile()) { + InputFile path = (InputFile) component.inputComponent(); + return new DefaultHighlightable((DefaultInputFile) path, sensorStorage, analysisMode); + } + return null; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/source/HighlightingCodeBuilder.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/HighlightingCodeBuilder.java new file mode 100644 index 00000000000..b807ee2d5e2 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/HighlightingCodeBuilder.java @@ -0,0 +1,91 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.source; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.sensor.highlighting.NewHighlighting; +import org.sonar.api.batch.sensor.highlighting.TypeOfText; +import org.sonar.colorizer.HtmlCodeBuilder; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class HighlightingCodeBuilder extends HtmlCodeBuilder { + + private static final Logger LOG = LoggerFactory.getLogger(HighlightingCodeBuilder.class); + + private int currentOffset = 0; + private static final Pattern START_TAG_PATTERN = Pattern.compile("<span class=\"(.+)\">"); + private static final Pattern END_TAG_PATTERN = Pattern.compile("</span>"); + private int startOffset = -1; + private String cssClass; + private final NewHighlighting highlighting; + + public HighlightingCodeBuilder(NewHighlighting highlighting) { + this.highlighting = highlighting; + } + + @Override + public Appendable append(CharSequence csq) { + for (int i = 0; i < csq.length(); i++) { + append(csq.charAt(i)); + } + return this; + } + + @Override + public Appendable append(char c) { + currentOffset++; + return this; + } + + @Override + public void appendWithoutTransforming(String htmlTag) { + if (startOffset == -1) { + Matcher startMatcher = START_TAG_PATTERN.matcher(htmlTag); + if (startMatcher.matches()) { + startOffset = currentOffset; + cssClass = startMatcher.group(1); + } else { + LOG.warn("Expected to match highlighting start html tag but was: " + htmlTag); + } + } else { + Matcher endMatcher = END_TAG_PATTERN.matcher(htmlTag); + if (endMatcher.matches()) { + highlighting.highlight(startOffset, currentOffset, TypeOfText.forCssClass(cssClass)); + startOffset = -1; + } else { + LOG.warn("Expected to match highlighting end html tag but was: " + htmlTag); + } + } + } + + @Override + public String toString() { + throw new UnsupportedOperationException(); + } + + @Override + public StringBuilder getColorizedCode() { + throw new UnsupportedOperationException(); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/source/HighlightingRenderer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/HighlightingRenderer.java new file mode 100644 index 00000000000..f4b9f3d399f --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/HighlightingRenderer.java @@ -0,0 +1,43 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.source; + +import org.sonar.api.batch.sensor.highlighting.NewHighlighting; +import org.sonar.channel.Channel; +import org.sonar.channel.CodeReader; +import org.sonar.colorizer.HtmlCodeBuilder; +import org.sonar.colorizer.TokenizerDispatcher; + +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; + +public class HighlightingRenderer { + + public void render(Reader code, List<? extends Channel<HtmlCodeBuilder>> tokenizers, NewHighlighting highlighting) { + List<Channel<HtmlCodeBuilder>> allTokenizers = new ArrayList<>(); + HighlightingCodeBuilder codeBuilder = new HighlightingCodeBuilder(highlighting); + + allTokenizers.addAll(tokenizers); + + new TokenizerDispatcher(allTokenizers).colorize(new CodeReader(code), codeBuilder); + highlighting.save(); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/source/LinesSensor.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/LinesSensor.java new file mode 100644 index 00000000000..d27c90953a0 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/LinesSensor.java @@ -0,0 +1,68 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.source; + +import org.sonar.api.batch.Phase; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.InputFile.Type; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +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.measure.internal.DefaultMeasure; +import org.sonar.api.measures.CoreMetrics; + +@Phase(name = Phase.Name.PRE) +public final class LinesSensor implements Sensor { + + @Override + public void describe(SensorDescriptor descriptor) { + descriptor.name("Lines Sensor"); + } + + @Override + public void execute(final SensorContext context) { + FileSystem fs = context.fileSystem(); + for (InputFile f : fs.inputFiles(fs.predicates().hasType(Type.MAIN))) { + ((DefaultMeasure<Integer>) context.<Integer>newMeasure() + .on(f) + .forMetric(CoreMetrics.LINES) + .withValue(f.lines())) + .setFromCore() + .save(); + if (f.language() == null) { + // As an approximation for files with no language plugin we consider every non blank line as ncloc + ((DefaultMeasure<Integer>) context.<Integer>newMeasure() + .on(f) + .forMetric(CoreMetrics.NCLOC) + .withValue(((DefaultInputFile) f).nonBlankLines())) + .save(); + // No test and no coverage on those files + ((DefaultMeasure<Integer>) context.<Integer>newMeasure() + .on(f) + .forMetric(CoreMetrics.LINES_TO_COVER) + .withValue(0)) + .save(); + } + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/source/SymbolizableBuilder.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/SymbolizableBuilder.java new file mode 100644 index 00000000000..fda6c4c7166 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/SymbolizableBuilder.java @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.source; + +import javax.annotation.CheckForNull; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.source.Symbolizable; +import org.sonar.batch.deprecated.perspectives.PerspectiveBuilder; +import org.sonar.batch.index.BatchComponent; +import org.sonar.batch.sensor.DefaultSensorStorage; + +public class SymbolizableBuilder extends PerspectiveBuilder<Symbolizable> { + + private final DefaultSensorStorage sensorStorage; + private final AnalysisMode analysisMode; + + public SymbolizableBuilder(DefaultSensorStorage sensorStorage, AnalysisMode analysisMode) { + super(Symbolizable.class); + this.sensorStorage = sensorStorage; + this.analysisMode = analysisMode; + } + + @CheckForNull + @Override + public Symbolizable loadPerspective(Class<Symbolizable> perspectiveClass, BatchComponent component) { + if (component.isFile()) { + InputFile path = (InputFile) component.inputComponent(); + return new DefaultSymbolizable((DefaultInputFile) path, sensorStorage, analysisMode); + } + return null; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/source/ZeroCoverageSensor.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/ZeroCoverageSensor.java new file mode 100644 index 00000000000..f211050dabc --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/ZeroCoverageSensor.java @@ -0,0 +1,112 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.source; + +import com.google.common.base.Function; +import com.google.common.collect.Sets; +import java.util.Map; +import java.util.Set; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.batch.Phase; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.InputFile.Type; +import org.sonar.api.batch.measure.Metric; +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.coverage.CoverageType; +import org.sonar.api.batch.sensor.coverage.NewCoverage; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.utils.KeyValueFormat; +import org.sonar.batch.scan.measure.MeasureCache; + +import static com.google.common.collect.Iterables.concat; +import static com.google.common.collect.Iterables.transform; +import static com.google.common.collect.Sets.newHashSet; + +@Phase(name = Phase.Name.POST) +public final class ZeroCoverageSensor implements Sensor { + + private static final class MeasureToMetricKey implements Function<Measure, String> { + @Override + public String apply(Measure input) { + return input.getMetricKey(); + } + } + + private static final class MetricToKey implements Function<Metric, String> { + @Override + public String apply(Metric input) { + return input.key(); + } + } + + private final MeasureCache measureCache; + + public ZeroCoverageSensor(MeasureCache measureCache) { + this.measureCache = measureCache; + } + + @Override + public void describe(SensorDescriptor descriptor) { + descriptor.name("Zero Coverage Sensor"); + } + + @Override + public void execute(final SensorContext context) { + FileSystem fs = context.fileSystem(); + for (InputFile f : fs.inputFiles(fs.predicates().hasType(Type.MAIN))) { + if (!isCoverageMeasuresAlreadyDefined(f)) { + Measure execLines = measureCache.byMetric(f.key(), CoreMetrics.EXECUTABLE_LINES_DATA_KEY); + if (execLines != null) { + storeZeroCoverageForEachExecutableLine(context, f, execLines); + } + + } + } + } + + private static void storeZeroCoverageForEachExecutableLine(final SensorContext context, InputFile f, Measure execLines) { + NewCoverage newCoverage = context.newCoverage().ofType(CoverageType.UNIT).onFile(f); + Map<Integer, String> lineMeasures = KeyValueFormat.parseIntString((String) execLines.value()); + for (Map.Entry<Integer, String> lineMeasure : lineMeasures.entrySet()) { + int lineIdx = lineMeasure.getKey(); + if (lineIdx <= f.lines()) { + String value = lineMeasure.getValue(); + if (StringUtils.isNotEmpty(value) && Integer.parseInt(value) > 0) { + newCoverage.lineHits(lineIdx, 0); + } + } + } + newCoverage.save(); + } + + private boolean isCoverageMeasuresAlreadyDefined(InputFile f) { + Set<String> metricKeys = newHashSet(transform(measureCache.byComponentKey(f.key()), new MeasureToMetricKey())); + Function<Metric, String> metricToKey = new MetricToKey(); + Set<String> allCoverageMetricKeys = newHashSet(concat(transform(CoverageType.UNIT.allMetrics(), metricToKey), + transform(CoverageType.IT.allMetrics(), metricToKey), + transform(CoverageType.OVERALL.allMetrics(), metricToKey))); + return !Sets.intersection(metricKeys, allCoverageMetricKeys).isEmpty(); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/source/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/package-info.java new file mode 100644 index 00000000000..c33229b0ab2 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/source/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.source; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/task/ListTask.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/task/ListTask.java new file mode 100644 index 00000000000..7c535674d82 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/task/ListTask.java @@ -0,0 +1,56 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.task; + +import org.sonar.api.task.Task; +import org.sonar.api.task.TaskDefinition; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; + +public class ListTask implements Task { + + private static final Logger LOG = Loggers.get(ListTask.class); + + public static final String KEY = "list"; + + public static final TaskDefinition DEFINITION = TaskDefinition.builder() + .key(KEY) + .description("List available tasks") + .taskClass(ListTask.class) + .build(); + + private final Tasks tasks; + + public ListTask(Tasks tasks) { + this.tasks = tasks; + } + + @Override + public void execute() { + StringBuilder sb = new StringBuilder(); + sb.append("\nAvailable tasks:\n"); + for (TaskDefinition def : tasks.definitions()) { + sb.append(" - " + def.key() + ": " + def.description() + "\n"); + } + sb.append("\n"); + LOG.info(sb.toString()); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/task/ScanTask.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/task/ScanTask.java new file mode 100644 index 00000000000..c5ba59c3d37 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/task/ScanTask.java @@ -0,0 +1,72 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.task; + +import javax.annotation.CheckForNull; +import org.sonar.api.CoreProperties; +import org.sonar.api.task.Task; +import org.sonar.api.task.TaskDefinition; +import org.sonar.batch.analysis.AnalysisProperties; +import org.sonar.batch.analysis.DefaultAnalysisMode; +import org.sonar.batch.bootstrap.GlobalProperties; +import org.sonar.batch.cache.ProjectSyncContainer; +import org.sonar.batch.scan.ProjectScanContainer; +import org.sonar.core.platform.ComponentContainer; + +public class ScanTask implements Task { + public static final TaskDefinition DEFINITION = TaskDefinition.builder() + .description("Scan project") + .key(CoreProperties.SCAN_TASK) + .taskClass(ScanTask.class) + .build(); + + private final ComponentContainer taskContainer; + private final TaskProperties taskProps; + + public ScanTask(TaskContainer taskContainer, TaskProperties taskProps) { + this.taskContainer = taskContainer; + this.taskProps = taskProps; + } + + @Override + public void execute() { + AnalysisProperties props = new AnalysisProperties(taskProps.properties(), taskProps.property(CoreProperties.ENCRYPTION_SECRET_KEY_PATH)); + if (isIssuesMode(props)) { + String projectKey = getProjectKeyWithBranch(props); + new ProjectSyncContainer(taskContainer, projectKey, false).execute(); + } + new ProjectScanContainer(taskContainer, props).execute(); + } + + @CheckForNull + private static String getProjectKeyWithBranch(AnalysisProperties props) { + String projectKey = props.property(CoreProperties.PROJECT_KEY_PROPERTY); + if (projectKey != null && props.property(CoreProperties.PROJECT_BRANCH_PROPERTY) != null) { + projectKey = projectKey + ":" + props.property(CoreProperties.PROJECT_BRANCH_PROPERTY); + } + return projectKey; + } + + private boolean isIssuesMode(AnalysisProperties props) { + DefaultAnalysisMode mode = new DefaultAnalysisMode(taskContainer.getComponentByType(GlobalProperties.class), props); + return mode.isIssues(); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/task/TaskContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/task/TaskContainer.java new file mode 100644 index 00000000000..eee91f63055 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/task/TaskContainer.java @@ -0,0 +1,91 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.task; + +import java.util.Map; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.InstantiationStrategy; +import org.sonar.api.config.EmailSettings; +import org.sonar.api.task.Task; +import org.sonar.api.task.TaskDefinition; +import org.sonar.api.utils.MessageException; +import org.sonar.batch.bootstrap.ExtensionInstaller; +import org.sonar.batch.bootstrap.ExtensionMatcher; +import org.sonar.batch.bootstrap.ExtensionUtils; +import org.sonar.batch.bootstrap.GlobalProperties; +import org.sonar.core.platform.ComponentContainer; + +public class TaskContainer extends ComponentContainer { + + private final Map<String, String> taskProperties; + private final Object[] components; + + public TaskContainer(ComponentContainer parent, Map<String, String> taskProperties, Object... components) { + super(parent); + this.taskProperties = taskProperties; + this.components = components; + } + + @Override + protected void doBeforeStart() { + addTaskExtensions(); + addCoreComponents(); + for (Object component : components) { + add(component); + } + } + + private void addCoreComponents() { + add(new TaskProperties(taskProperties, getParent().getComponentByType(GlobalProperties.class).property(CoreProperties.ENCRYPTION_SECRET_KEY_PATH))); + add(EmailSettings.class); + } + + private void addTaskExtensions() { + getComponentByType(ExtensionInstaller.class).install(this, new TaskExtensionFilter()); + } + + static class TaskExtensionFilter implements ExtensionMatcher { + @Override + public boolean accept(Object extension) { + return ExtensionUtils.isBatchSide(extension) + && ExtensionUtils.isInstantiationStrategy(extension, InstantiationStrategy.PER_TASK); + } + } + + @Override + public void doAfterStart() { + // default value is declared in CorePlugin + String taskKey = StringUtils.defaultIfEmpty(taskProperties.get(CoreProperties.TASK), CoreProperties.SCAN_TASK); + // Release memory + taskProperties.clear(); + + TaskDefinition def = getComponentByType(Tasks.class).definition(taskKey); + if (def == null) { + throw MessageException.of("Task '" + taskKey + "' does not exist. Please use '" + ListTask.KEY + "' task to see all available tasks."); + } + Task task = getComponentByType(def.taskClass()); + if (task != null) { + task.execute(); + } else { + throw new IllegalStateException("Task " + taskKey + " is badly defined"); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/task/TaskProperties.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/task/TaskProperties.java new file mode 100644 index 00000000000..b8470ce4d00 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/task/TaskProperties.java @@ -0,0 +1,36 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.task; + +import java.util.Map; +import javax.annotation.Nullable; +import org.sonar.batch.bootstrap.UserProperties; + +/** + * Batch properties that are specific to a task (for example + * coming from sonar-project.properties). + */ +public class TaskProperties extends UserProperties { + + public TaskProperties(Map<String, String> properties, @Nullable String pathToSecretKey) { + super(properties, pathToSecretKey); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/task/Tasks.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/task/Tasks.java new file mode 100644 index 00000000000..ee68b62b1db --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/task/Tasks.java @@ -0,0 +1,75 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.task; + +import com.google.common.collect.ImmutableSortedMap; +import com.google.common.collect.Maps; +import java.util.Collection; +import java.util.Map; +import java.util.SortedMap; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.InstantiationStrategy; +import org.sonar.api.task.Task; +import org.sonar.api.task.TaskDefinition; + +@BatchSide +@InstantiationStrategy(InstantiationStrategy.PER_TASK) +public class Tasks { + + private final SortedMap<String, TaskDefinition> byKey; + + public Tasks(TaskDefinition[] definitions) { + SortedMap<String, TaskDefinition> map = Maps.newTreeMap(); + for (TaskDefinition definition : definitions) { + if (map.containsKey(definition.key())) { + throw new IllegalStateException("Task '" + definition.key() + "' is declared twice"); + } + map.put(definition.key(), definition); + } + this.byKey = ImmutableSortedMap.copyOf(map); + } + + public TaskDefinition definition(String taskKey) { + return byKey.get(taskKey); + } + + public Collection<TaskDefinition> definitions() { + return byKey.values(); + } + + /** + * Perform validation of task definitions + */ + public void start() { + checkDuplicatedClasses(); + } + + private void checkDuplicatedClasses() { + Map<Class<? extends Task>, TaskDefinition> byClass = Maps.newHashMap(); + for (TaskDefinition def : definitions()) { + TaskDefinition other = byClass.get(def.taskClass()); + if (other == null) { + byClass.put(def.taskClass(), def); + } else { + throw new IllegalStateException("Task '" + def.taskClass().getName() + "' is defined twice: first by '" + other.key() + "' and then by '" + def.key() + "'"); + } + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/task/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/task/package-info.java new file mode 100644 index 00000000000..5787c1e27c2 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/task/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.task; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/test/DefaultCoverageBlock.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/test/DefaultCoverageBlock.java new file mode 100644 index 00000000000..52c3ca7a76f --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/test/DefaultCoverageBlock.java @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.test; + +import java.util.List; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.test.CoverageBlock; +import org.sonar.api.test.TestCase; +import org.sonar.api.test.Testable; + +public class DefaultCoverageBlock implements CoverageBlock { + + private final TestCase testCase; + private final DefaultInputFile testable; + private final List<Integer> lines; + + public DefaultCoverageBlock(TestCase testCase, DefaultInputFile testable, List<Integer> lines) { + this.testCase = testCase; + this.testable = testable; + this.lines = lines; + } + + @Override + public TestCase testCase() { + return testCase; + } + + @Override + public Testable testable() { + return new DefaultTestable(testable); + } + + @Override + public List<Integer> lines() { + return lines; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/test/DefaultTestCase.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/test/DefaultTestCase.java new file mode 100644 index 00000000000..652d11a0ddb --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/test/DefaultTestCase.java @@ -0,0 +1,163 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.test; + +import com.google.common.base.Preconditions; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.InputFile.Type; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.test.CoverageBlock; +import org.sonar.api.test.MutableTestCase; +import org.sonar.api.test.TestPlan; +import org.sonar.api.test.Testable; +import org.sonar.api.test.exception.CoverageAlreadyExistsException; +import org.sonar.api.test.exception.IllegalDurationException; + +public class DefaultTestCase implements MutableTestCase { + + private final DefaultTestPlan testPlan; + private String type; + private Long durationInMs; + private Status status; + private String name; + private String message; + private String stackTrace; + private Map<DefaultInputFile, CoverageBlock> coverageBlocksByTestedFile = new LinkedHashMap<>(); + + public DefaultTestCase(DefaultTestPlan testPlan) { + this.testPlan = testPlan; + } + + @Override + public String type() { + return type; + } + + @Override + public MutableTestCase setType(@Nullable String s) { + this.type = s; + return this; + } + + @Override + public Long durationInMs() { + return durationInMs; + } + + @Override + public MutableTestCase setDurationInMs(@Nullable Long l) { + if (l != null && l < 0) { + throw new IllegalDurationException("Test duration must be positive (got: " + l + ")"); + } + this.durationInMs = l; + return this; + } + + @Override + public Status status() { + return status; + } + + @Override + public MutableTestCase setStatus(@Nullable Status s) { + this.status = s; + return this; + } + + @Override + public String name() { + return name; + } + + public MutableTestCase setName(String s) { + this.name = s; + return this; + } + + @Override + public String message() { + return message; + } + + @Override + public MutableTestCase setMessage(String s) { + this.message = s; + return this; + } + + @Override + public String stackTrace() { + return stackTrace; + } + + @Override + public MutableTestCase setStackTrace(String s) { + this.stackTrace = s; + return this; + } + + @Override + public MutableTestCase setCoverageBlock(Testable testable, List<Integer> lines) { + DefaultInputFile coveredFile = ((DefaultTestable) testable).inputFile(); + return setCoverageBlock(coveredFile, lines); + } + + @Override + public MutableTestCase setCoverageBlock(InputFile mainFile, List<Integer> lines) { + Preconditions.checkArgument(mainFile.type() == Type.MAIN, "Test file can only cover a main file"); + DefaultInputFile coveredFile = (DefaultInputFile) mainFile; + if (coverageBlocksByTestedFile.containsKey(coveredFile)) { + throw new CoverageAlreadyExistsException("The link between " + name() + " and " + coveredFile.key() + " already exists"); + } + coverageBlocksByTestedFile.put(coveredFile, new DefaultCoverageBlock(this, coveredFile, lines)); + return this; + } + + @Override + public TestPlan testPlan() { + return testPlan; + } + + @Override + public boolean doesCover() { + return !coverageBlocksByTestedFile.isEmpty(); + } + + @Override + public int countCoveredLines() { + throw new UnsupportedOperationException("Not supported since SQ 5.2"); + } + + @Override + public Iterable<CoverageBlock> coverageBlocks() { + return coverageBlocksByTestedFile.values(); + } + + @Override + public CoverageBlock coverageBlock(final Testable testable) { + DefaultInputFile coveredFile = ((DefaultTestable) testable).inputFile(); + return coverageBlocksByTestedFile.get(coveredFile); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/test/DefaultTestPlan.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/test/DefaultTestPlan.java new file mode 100644 index 00000000000..85b26082424 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/test/DefaultTestPlan.java @@ -0,0 +1,57 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.test; + +import com.google.common.collect.Lists; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.CheckForNull; +import org.sonar.api.test.MutableTestCase; +import org.sonar.api.test.MutableTestPlan; + +public class DefaultTestPlan implements MutableTestPlan { + private List<MutableTestCase> testCases = new ArrayList<>(); + + @Override + @CheckForNull + public Iterable<MutableTestCase> testCasesByName(String name) { + List<MutableTestCase> result = Lists.newArrayList(); + for (MutableTestCase testCase : testCases()) { + if (name.equals(testCase.name())) { + result.add(testCase); + } + } + return result; + } + + @Override + public MutableTestCase addTestCase(String name) { + DefaultTestCase testCase = new DefaultTestCase(this); + testCase.setName(name); + testCases.add(testCase); + return testCase; + } + + @Override + public Iterable<MutableTestCase> testCases() { + return testCases; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/test/DefaultTestable.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/test/DefaultTestable.java new file mode 100644 index 00000000000..c672253920e --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/test/DefaultTestable.java @@ -0,0 +1,86 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.test; + +import java.util.List; +import java.util.Map; +import java.util.SortedSet; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.test.CoverageBlock; +import org.sonar.api.test.MutableTestable; +import org.sonar.api.test.TestCase; + +public class DefaultTestable implements MutableTestable { + + private final DefaultInputFile inputFile; + + public DefaultTestable(DefaultInputFile inputFile) { + this.inputFile = inputFile; + } + + public DefaultInputFile inputFile() { + return inputFile; + } + + @Override + public List<TestCase> testCases() { + throw unsupported(); + } + + @Override + public TestCase testCaseByName(final String name) { + throw unsupported(); + } + + @Override + public int countTestCasesOfLine(Integer line) { + throw unsupported(); + } + + @Override + public Map<Integer, Integer> testCasesByLines() { + throw unsupported(); + } + + @Override + public List<TestCase> testCasesOfLine(int line) { + throw unsupported(); + } + + @Override + public SortedSet<Integer> testedLines() { + throw unsupported(); + } + + @Override + public CoverageBlock coverageBlock(final TestCase testCase) { + throw unsupported(); + } + + @Override + public Iterable<CoverageBlock> coverageBlocks() { + throw unsupported(); + } + + private static UnsupportedOperationException unsupported() { + return new UnsupportedOperationException("No more available since SQ 5.2"); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/test/TestPlanBuilder.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/test/TestPlanBuilder.java new file mode 100644 index 00000000000..b3bab1ee6f5 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/test/TestPlanBuilder.java @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.test; + +import java.util.HashMap; +import java.util.Map; +import javax.annotation.CheckForNull; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.InputFile.Type; +import org.sonar.api.test.MutableTestPlan; +import org.sonar.batch.deprecated.perspectives.PerspectiveBuilder; +import org.sonar.batch.index.BatchComponent; + +public class TestPlanBuilder extends PerspectiveBuilder<MutableTestPlan> { + + private Map<InputFile, DefaultTestPlan> testPlanByFile = new HashMap<>(); + + public TestPlanBuilder() { + super(MutableTestPlan.class); + } + + @CheckForNull + @Override + public MutableTestPlan loadPerspective(Class<MutableTestPlan> perspectiveClass, BatchComponent component) { + if (component.isFile()) { + InputFile inputFile = (InputFile) component.inputComponent(); + if (inputFile.type() == Type.TEST) { + if (!testPlanByFile.containsKey(inputFile)) { + testPlanByFile.put(inputFile, new DefaultTestPlan()); + } + return testPlanByFile.get(inputFile); + } + } + return null; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/test/TestableBuilder.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/test/TestableBuilder.java new file mode 100644 index 00000000000..261b0b15a1d --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/test/TestableBuilder.java @@ -0,0 +1,47 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.test; + +import javax.annotation.CheckForNull; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.InputFile.Type; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.test.MutableTestable; +import org.sonar.batch.deprecated.perspectives.PerspectiveBuilder; +import org.sonar.batch.index.BatchComponent; + +public class TestableBuilder extends PerspectiveBuilder<MutableTestable> { + + public TestableBuilder() { + super(MutableTestable.class); + } + + @CheckForNull + @Override + public MutableTestable loadPerspective(Class<MutableTestable> perspectiveClass, BatchComponent component) { + if (component.isFile()) { + InputFile inputFile = (InputFile) component.inputComponent(); + if (inputFile.type() == Type.MAIN) { + return new DefaultTestable((DefaultInputFile) inputFile); + } + } + return null; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/test/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/test/package-info.java new file mode 100644 index 00000000000..cb0a2f6c3e7 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/test/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.test; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/util/BatchUtils.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/util/BatchUtils.java new file mode 100644 index 00000000000..81eed874378 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/util/BatchUtils.java @@ -0,0 +1,87 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.util; + +import com.google.common.base.Strings; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BatchUtils { + private static final Logger LOG = LoggerFactory.getLogger(BatchUtils.class); + + private BatchUtils() { + } + + /** + * Clean provided string to remove chars that are not valid as file name. + * @param projectKey e.g. my:file + */ + public static String cleanKeyForFilename(String projectKey) { + String cleanKey = StringUtils.deleteWhitespace(projectKey); + return StringUtils.replace(cleanKey, ":", "_"); + } + + public static String encodeForUrl(@Nullable String url) { + try { + return URLEncoder.encode(Strings.nullToEmpty(url), "UTF-8"); + + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException("Encoding not supported", e); + } + } + + public static String describe(Object o) { + try { + if (o.getClass().getMethod("toString").getDeclaringClass() != Object.class) { + return o.toString(); + } + } catch (Exception e) { + // fallback + } + + return o.getClass().getName(); + } + + @CheckForNull + public static String getServerVersion() { + InputStream is = BatchUtils.class.getResourceAsStream("/sq-version.txt"); + if (is == null) { + LOG.warn("Failed to get SQ version"); + return null; + } + try (BufferedReader br = IOUtils.toBufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { + return br.readLine(); + } catch (IOException e) { + LOG.warn("Failed to get SQ version", e); + return null; + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/util/ProgressReport.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/util/ProgressReport.java new file mode 100644 index 00000000000..f2ea0406a01 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/util/ProgressReport.java @@ -0,0 +1,79 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.util; + +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; + +public class ProgressReport implements Runnable { + + private static final Logger LOG = Loggers.get(ProgressReport.class); + private final long period; + private String message = ""; + private final Thread thread; + private String stopMessage = ""; + + public ProgressReport(String threadName, long period) { + this.period = period; + thread = new Thread(this); + thread.setName(threadName); + thread.setDaemon(true); + } + + @Override + public void run() { + while (!Thread.interrupted()) { + try { + Thread.sleep(period); + log(message); + } catch (InterruptedException e) { + break; + } + } + log(stopMessage); + } + + public void start(String startMessage) { + log(startMessage); + thread.start(); + } + + public void message(String message) { + this.message = message; + } + + public void stop(String stopMessage) { + this.stopMessage = stopMessage; + thread.interrupt(); + try { + thread.join(); + } catch (InterruptedException e) { + // Ignore + } + } + + private static void log(String message) { + synchronized (LOG) { + LOG.info(message); + LOG.notifyAll(); + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/util/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/util/package-info.java new file mode 100644 index 00000000000..42cf7aa9450 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/util/package-info.java @@ -0,0 +1,22 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@javax.annotation.ParametersAreNonnullByDefault +package org.sonar.batch.util; + diff --git a/sonar-scanner-engine/src/main/resources/org/sonar/batch/bootstrapper/logback.xml b/sonar-scanner-engine/src/main/resources/org/sonar/batch/bootstrapper/logback.xml new file mode 100644 index 00000000000..99f956e4b4c --- /dev/null +++ b/sonar-scanner-engine/src/main/resources/org/sonar/batch/bootstrapper/logback.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<configuration debug="false"> + + <!-- + + This file is loaded by bootstrappers like Ant Task and Java Runner. + + Reasons to NOT move this configuration to bootstrappers: + - same lifecycle as sonar -> loggers are always up-to-date. No need to think about ascending/descending compatibility. + - parameters can be added without releasing new versions of bootstrappers + - XML format is up-to-date toward the version of Logback. + + --> + + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <pattern>${FORMAT}</pattern> + </encoder> + </appender> + + <!-- BeanUtils generate to many DEBUG logs when sonar.verbose is set --> + <logger name="org.apache.commons.beanutils.converters"> + <level value="WARN"/> + </logger> + + <!-- sonar.showSql --> + <!-- see also org.sonar.db.MyBatis#configureLogback() --> + <logger name="org.mybatis"> + <level value="${SQL_LOGGER_LEVEL:-WARN}"/> + </logger> + <logger name="org.apache.ibatis"> + <level value="${SQL_LOGGER_LEVEL:-WARN}"/> + </logger> + <logger name="java.sql"> + <level value="${SQL_LOGGER_LEVEL:-WARN}"/> + </logger> + <logger name="java.sql.ResultSet"> + <level value="WARN"/> + </logger> + <logger name="PERSISTIT"> + <level value="WARN"/> + </logger> + + <root> + <!-- sonar.verbose --> + <level value="${ROOT_LOGGER_LEVEL}"/> + <appender-ref ref="STDOUT"/> + </root> + +</configuration> diff --git a/sonar-scanner-engine/src/main/resources/org/sonar/batch/logback.xml b/sonar-scanner-engine/src/main/resources/org/sonar/batch/logback.xml new file mode 100644 index 00000000000..198cd9c1bc6 --- /dev/null +++ b/sonar-scanner-engine/src/main/resources/org/sonar/batch/logback.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<configuration debug="false"> + + <!-- + + This file is deprecated. It's replaced by org/sonar/batch/bootstrapper/logback.xml. + It can't be deleted as long as Ant Task and Java Runner do not use org.sonar.batch.bootstrapper.LoggingConfiguration. + + --> + + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <pattern>%d{HH:mm:ss.SSS} %-5level %20.20logger{20} - %msg%n</pattern> + </encoder> + </appender> + + <!-- BeanUtils generate to many DEBUG logs when sonar.verbose is set --> + <logger name="org.apache.commons.beanutils.converters"> + <level value="WARN"/> + </logger> + + <!-- sonar.showSql --> + <!-- see also org.sonar.db.MyBatis#configureLogback() --> + <logger name="org.apache.ibatis"> + <level value="WARN"/> + </logger> + <logger name="org.mybatis"> + <level value="WARN"/> + </logger> + <logger name="java.sql"> + <level value="WARN"/> + </logger> + <logger name="java.sql.ResultSet"> + <level value="WARN"/> + </logger> + <logger name="PERSISTIT"> + <level value="WARN"/> + </logger> + + + <root> + <!-- sonar.verbose --> + <level value="${ROOT_LOGGER_LEVEL}"/> + <appender-ref ref="STDOUT"/> + </root> + +</configuration> diff --git a/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport.ftl b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport.ftl new file mode 100644 index 00000000000..c41b8067222 --- /dev/null +++ b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport.ftl @@ -0,0 +1,462 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <title>Issues report of ${report.getTitle()?html}</title> + <link href="issuesreport_files/sonar.css" media="all" rel="stylesheet" type="text/css"> + <link rel="shortcut icon" type="image/x-icon" href="issuesreport_files/favicon.ico"> + <script type="text/javascript" src="issuesreport_files/jquery.min.js"></script> + <script type="text/javascript"> + var issuesPerResource = [ + <#list report.getResourceReports() as resourceReport> + [ + <#assign issues=resourceReport.getIssues()> + <#list issues as issue> + <#if complete || issue.isNew()> + {'k': '${issue.key()}', 'r': 'R${issue.getRuleKey()}', 'l': ${(issue.startLine()!0)?c}, 'new': ${issue.isNew()?string}, 's': '${issue.severity()?lower_case}'}<#if issue_has_next>,</#if> + </#if> + </#list> + ] + <#if resourceReport_has_next>,</#if> + </#list> + ]; + var nbResources = ${report.getResourcesWithReport()?size?c}; + var separators = new Array(); + + function showLine(fileIndex, lineId) { + var elt = $('#' + fileIndex + 'L' + lineId); + if (elt != null) { + elt.show(); + } + elt = $('#' + fileIndex + 'LV' + lineId); + if (elt != null) { + elt.show(); + } + } + + /* lineIds must be sorted */ + function showLines(fileIndex, lineIds) { + var lastSeparatorId = 9999999; + for (var lineIndex = 0; lineIndex < lineIds.length; lineIndex++) { + var lineId = lineIds[lineIndex]; + if (lineId > 0) { + if (lineId > lastSeparatorId) { + var separator = $('#' + fileIndex + 'S' + lastSeparatorId); + if (separator != null) { + separator.addClass('visible'); + separators.push(separator); + } + } + + for (var i = -2; i < 3; ++i) { + showLine(fileIndex, lineId + i); + } + lastSeparatorId = lineId + 2; + } + } + } + function hideAll() { + $('tr.row').hide(); + $('div.issue').hide(); + for (var separatorIndex = 0; separatorIndex < separators.length; separatorIndex++) { + separators[separatorIndex].removeClass('visible'); + } + separators.length = 0; + $('.sources td.ko').removeClass('ko'); + } + + function showIssues(fileIndex, issues) { + $.each(issues, function(index, issue) { + $('#' + issue['k']).show(); + $('#' + fileIndex + 'L' + issue['l'] + ' td.line').addClass('ko'); + }); + var showResource = issues.length > 0; + if (showResource) { + $('#resource-' + fileIndex).show(); + } else { + $('#resource-' + fileIndex).hide(); + } + } + + + function refreshFilters(updateSelect) { + <#if complete> + var onlyNewIssues = $('#new_filter').is(':checked'); + <#else> + var onlyNewIssues = true; + </#if> + + if (updateSelect) { + populateSelectFilter(onlyNewIssues); + } + var ruleFilter = $('#rule_filter').val(); + + hideAll(); + if (onlyNewIssues) { + $('.all').addClass('all-masked'); + } else { + $('.all').removeClass('all-masked'); + } + for (var resourceIndex = 0; resourceIndex < nbResources; resourceIndex++) { + var filteredIssues = $.grep(issuesPerResource[resourceIndex], function(v) { + return (!onlyNewIssues || v['new']) && (ruleFilter == '' || v['r'] == ruleFilter || v['s'] == ruleFilter); + } + ); + + var linesToDisplay = $.map(filteredIssues, function(v, i) { + return v['l']; + }); + + linesToDisplay.sort();// the showLines() requires sorted ids + showLines(resourceIndex, linesToDisplay); + showIssues(resourceIndex, filteredIssues); + } + } + + + var severityFilter = [ + <#assign severities = report.getSummary().getTotalBySeverity()> + <#list severities?keys as severity> + { "key": "${severity?lower_case}", + "label": "${severity?lower_case?cap_first}", + "total": ${severities[severity].getCountInCurrentAnalysis()?c}, + "newtotal": ${severities[severity].getNewIssuesCount()?c} + }<#if severity_has_next>,</#if> + </#list> + ]; + + var ruleFilter = [ + <#assign rules = report.getSummary().getTotalByRuleKey()> + <#list rules?keys as ruleKey> + { "key": "${ruleKey}", + "label": "${ruleNameProvider.nameForJS(ruleKey)}", + "total": ${rules[ruleKey].getCountInCurrentAnalysis()?c}, + "newtotal": ${rules[ruleKey].getNewIssuesCount()?c} + }<#if ruleKey_has_next>,</#if> + </#list> + ].sort(function(a, b) { + var x = a.label; var y = b.label; + return ((x < y) ? -1 : ((x > y) ? 1 : 0)); + }); + + function populateSelectFilter(onlyNewIssues) { + var ruleFilterSelect = $('#rule_filter'); + ruleFilterSelect.empty().append(function() { + var output = ''; + output += '<option value="" selected>Filter by:</option>'; + output += '<optgroup label="Severity">'; + $.each(severityFilter, function(key, value) { + if ((!onlyNewIssues && value.total > 0) || value.newtotal > 0) { + output += '<option value="' + value.key + '">' + value.label + ' (' + (onlyNewIssues ? value.newtotal : value.total) + ')</option>'; + } + }); + output += '<optgroup label="Rule">'; + $.each(ruleFilter, function(key, value) { + if ((!onlyNewIssues && value.total > 0) || value.newtotal > 0) { + output += '<option value="R' + value.key + '">' + value.label + ' (' + (onlyNewIssues ? value.newtotal : value.total) + ')</option>'; + } + }); + return output; + }); + } + </script> +</head> +<body> +<div id="reportHeader"> + <div id="logo"><img src="issuesreport_files/sonarqube-24x100.png" alt="SonarQube"/></div> + <div class="title">Issues Report</div> + <div class="subtitle">${report.getTitle()?html} - ${report.getDate()?datetime}</div> +</div> + +<#if report.isNoFile()> +<div id="content"> + <div class="banner">No file analyzed</div> +</div> +<#else> +<div id="content"> + + <#if !complete> + <div class="banner">Light report: only new issues are displayed</div> + </#if> + + <div id="summary"> + <table width="100%"> + <tbody> + <tr> + <#if complete> + <#assign size = '33'> + <#else> + <#assign size = '50'> + </#if> + <td align="center" width="${size}%"> + <h3>New issues</h3> + <#if report.getSummary().getTotal().getNewIssuesCount() gt 0> + <span class="big worst">${report.getSummary().getTotal().getNewIssuesCount()?c}</span> + <#else> + <span class="big">0</span> + </#if> + </td> + <td align="center" width="${size}%"> + <h3>Resolved issues</h3> + <#if report.getSummary().getTotal().getResolvedIssuesCount() gt 0> + <span class="big better">${report.getSummary().getTotal().getResolvedIssuesCount()?c}</span> + <#else> + <span class="big">0</span> + </#if> + </td> + <#if complete> + <td align="center" width="${size}%" class="all"> + <h3>Issues</h3> + <span class="big">${report.getSummary().getTotal().getCountInCurrentAnalysis()?c}</span> + </td> + </#if> + </tr> + </tbody> + </table> + <#if complete> + <br/> + <table width="100%" class="data"> + <thead> + <tr class="total"> + <th colspan="2" align="left"> + Issues per Rule + </th> + <th align="right" width="1%" nowrap>New issues</th> + <th align="right" width="1%" nowrap>Resolved issues</th> + <th align="right" width="1%" nowrap class="all">Issues</th> + </tr> + </thead> + <tbody> + <#list report.getSummary().getRuleReports() as ruleReport> + <#if complete || (ruleReport.getTotal().getNewIssuesCount() > 0)> + <#if ruleReport.getTotal().getNewIssuesCount() = 0> + <#assign trCss = 'all'> + <#else> + <#assign trCss = ''> + </#if> + <tr class="hoverable ${trCss}"> + <td width="20"> + <i class="icon-severity-${ruleReport.getSeverity()?lower_case}"></i> + </td> + <td align="left"> + ${ruleNameProvider.nameForHTML(ruleReport.getRule())} + </td> + <td align="right"> + <#if ruleReport.getTotal().getNewIssuesCount() gt 0> + <span class="worst">${ruleReport.getTotal().getNewIssuesCount()?c}</span> + <#else> + <span>0</span> + </#if> + </td> + <td align="right"> + <#if ruleReport.getTotal().getResolvedIssuesCount() gt 0> + <span class="better">${ruleReport.getTotal().getResolvedIssuesCount()?c}</span> + <#else> + <span>0</span> + </#if> + </td> + <td align="right" class="all"> + ${ruleReport.getTotal().getCountInCurrentAnalysis()?c} + </td> + </tr> + </#if> + </#list> + </tbody> + </table> + </#if> + </div> + + <br/> + + <div class="banner"> + <#if complete> + <input type="checkbox" id="new_filter" onclick="refreshFilters(true)" checked="checked" /> <label for="new_filter">Only NEW + issues</label> + + </#if> + + <select id="rule_filter" onchange="refreshFilters(false)"> + </select> + </div> + + <div id="summary-per-file"> + <#list report.getResourceReports() as resourceReport> + <#if complete || (resourceReport.getTotal().getNewIssuesCount() > 0)> + <#assign issueId=0> + <#if resourceReport.getTotal().getNewIssuesCount() = 0> + <#assign tableCss = 'all'> + <#else> + <#assign tableCss = ''> + </#if> + <table width="100%" class="data ${tableCss}" id="resource-${resourceReport_index?c}"> + <thead> + <tr class="total"> + <th align="left" colspan="2" nowrap> + <div class="file_title"> + <img src="issuesreport_files/${resourceReport.getType()}.png" title="Resource icon"/> + <a href="#" onclick="$('.resource-details-${resourceReport_index?c}').toggleClass('masked'); return false;" style="color: black">${resourceReport.getName()}</a> + </div> + </th> + <th align="right" width="1%" nowrap class="resource-details-${resourceReport_index?c}"> + <#if resourceReport.getTotal().getNewIssuesCount() gt 0> + <span class="worst" id="new-total">${resourceReport.getTotal().getNewIssuesCount()?c}</span> + <#else> + <span id="new-total">0</span> + </#if> + <br/>New issues + </th> + <#if complete> + <th align="right" width="1%" nowrap class="resource-details-${resourceReport_index?c}"> + <#if resourceReport.getTotal().getResolvedIssuesCount() gt 0> + <span class="better" id="resolved-total">${resourceReport.getTotal().getResolvedIssuesCount()?c}</span> + <#else> + <span id="resolved-total">0</span> + </#if> + <br/>Resolved issues + </th> + <th align="right" width="1%" nowrap class="resource-details-${resourceReport_index?c} all"> + <span id="current-total">${resourceReport.getTotal().getCountInCurrentAnalysis()?c}</span><br/>Issues + </th> + </#if> + </tr> + </thead> + <tbody class="resource-details-${resourceReport_index?c}"> + <#if complete> + <#list resourceReport.getRuleReports() as ruleReport> + <tr class="hoverable all"> + <td width="20"> + <i class="icon-severity-${ruleReport.getSeverity()?lower_case}"></i> + </td> + <td align="left"> + ${ruleNameProvider.nameForHTML(ruleReport.getRule())} + </td> + <td align="right"> + <#if ruleReport.getTotal().getNewIssuesCount() gt 0> + <span class="worst">${ruleReport.getTotal().getNewIssuesCount()?c}</span> + <#else> + <span>0</span> + </#if> + </td> + <#if complete> + <td align="right"> + <#if ruleReport.getTotal().getResolvedIssuesCount() gt 0> + <span class="better">${ruleReport.getTotal().getResolvedIssuesCount()?c}</span> + <#else> + <span>0</span> + </#if> + </td> + <td align="right" class="all"> + ${ruleReport.getTotal().getCountInCurrentAnalysis()?c} + </td> + </#if> + </tr> + </#list> + </#if> + <#if complete> + <#assign colspan = '5'> + <#else> + <#assign colspan = '3'> + </#if> + <#assign issues=resourceReport.getIssuesAtLine(0, complete)> + <#if issues?has_content> + <tr class="globalIssues"> + <td colspan="${colspan}"> + <#list issues as issue> + <div class="issue" id="${issue.key()}"> + <div class="vtitle"> + <i class="icon-severity-${issue.severity()?lower_case}"></i> + <#if issue.getMessage()?has_content> + <span class="rulename">${issue.getMessage()?html}</span> + <#else> + <span class="rulename">${ruleNameProvider.nameForHTML(issue.getRuleKey())}</span> + </#if> + + <img src="issuesreport_files/sep12.png"> + + <span class="issue_date"> + <#if issue.isNew()> + NEW + <#else> + ${issue.creationDate()?date} + </#if> + </span> + </div> + <div class="discussionComment"> + ${ruleNameProvider.nameForHTML(issue.getRuleKey())} + </div> + </div> + <#assign issueId = issueId + 1> + </#list> + </td> + </tr> + </#if> + <tr> + <td colspan="${colspan}"> + <table class="sources" border="0" cellpadding="0" cellspacing="0"> + <#list sourceProvider.getEscapedSource(resourceReport.getResourceNode()) as line> + <#assign lineIndex=line_index+1> + <#if resourceReport.isDisplayableLine(lineIndex, complete)> + <tr id="${resourceReport_index?c}L${lineIndex?c}" class="row"> + <td class="lid ">${lineIndex?c}</td> + <td class="line "> + <pre>${line}</pre> + </td> + </tr> + <tr id="${resourceReport_index}S${lineIndex?c}" class="blockSep"> + <td colspan="2"></td> + </tr> + <#assign issues=resourceReport.getIssuesAtLine(lineIndex, complete)> + <#if issues?has_content> + <tr id="${resourceReport_index?c}LV${lineIndex?c}" class="row"> + <td class="lid"></td> + <td class="issues"> + <#list issues as issue> + <div class="issue" id="${issue.key()}"> + <div class="vtitle"> + <i class="icon-severity-${issue.severity()?lower_case}"></i> + <#if issue.getMessage()?has_content> + <span class="rulename">${issue.getMessage()?html}</span> + <#else> + <span class="rulename">${ruleNameProvider.nameForHTML(issue.getRuleKey())}</span> + </#if> + + <img src="issuesreport_files/sep12.png"> + + <span class="issue_date"> + <#if issue.isNew()> + NEW + <#else> + ${issue.creationDate()?date} + </#if> + </span> + + + </div> + <div class="discussionComment"> + ${ruleNameProvider.nameForHTML(issue.getRuleKey())} + </div> + </div> + <#assign issueId = issueId + 1> + </#list> + </td> + </tr> + </#if> + </#if> + </#list> + </table> + </td> + </tr> + </tbody> + </table> + </#if> + </#list> + </div> +</div> +<script type="text/javascript"> + $(function() { + refreshFilters(true); + }); +</script> +</#if> +</body> +</html> diff --git a/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/DIR.png b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/DIR.png Binary files differnew file mode 100644 index 00000000000..b135ef92eec --- /dev/null +++ b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/DIR.png diff --git a/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/FIL.png b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/FIL.png Binary files differnew file mode 100644 index 00000000000..1664e25c8b5 --- /dev/null +++ b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/FIL.png diff --git a/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/PRJ.png b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/PRJ.png Binary files differnew file mode 100644 index 00000000000..b32e51c5f42 --- /dev/null +++ b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/PRJ.png diff --git a/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/favicon.ico b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/favicon.ico Binary files differnew file mode 100644 index 00000000000..c6d382d9823 --- /dev/null +++ b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/favicon.ico diff --git a/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/jquery.min.js b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/jquery.min.js new file mode 100644 index 00000000000..53763631337 --- /dev/null +++ b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/jquery.min.js @@ -0,0 +1,6 @@ +/*! jQuery v1.10.1 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license + //@ sourceMappingURL=jquery-1.10.1.min.map + */ +(function(e,t){var n,r,i=typeof t,o=e.location,a=e.document,s=a.documentElement,l=e.jQuery,u=e.$,c={},p=[],f="1.10.1",d=p.concat,h=p.push,g=p.slice,m=p.indexOf,y=c.toString,v=c.hasOwnProperty,b=f.trim,x=function(e,t){return new x.fn.init(e,t,r)},w=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=/\S+/g,C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,k=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,E=/^[\],:{}\s]*$/,S=/(?:^|:|,)(?:\s*\[)+/g,A=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,j=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,D=/^-ms-/,L=/-([\da-z])/gi,H=function(e,t){return t.toUpperCase()},q=function(e){(a.addEventListener||"load"===e.type||"complete"===a.readyState)&&(_(),x.ready())},_=function(){a.addEventListener?(a.removeEventListener("DOMContentLoaded",q,!1),e.removeEventListener("load",q,!1)):(a.detachEvent("onreadystatechange",q),e.detachEvent("onload",q))};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof x?n[0]:n,x.merge(this,x.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:a,!0)),k.test(i[1])&&x.isPlainObject(n))for(i in n)x.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=a.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=a,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return g.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(g.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},l=2),"object"==typeof s||x.isFunction(s)||(s={}),u===l&&(s=this,--l);u>l;l++)if(null!=(o=arguments[l]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(x.isPlainObject(r)||(n=x.isArray(r)))?(n?(n=!1,a=e&&x.isArray(e)?e:[]):a=e&&x.isPlainObject(e)?e:{},s[i]=x.extend(c,a,r)):r!==t&&(s[i]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=l),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){if(e===!0?!--x.readyWait:!x.isReady){if(!a.body)return setTimeout(x.ready);x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(a,[x]),x.fn.trigger&&x(a).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray||function(e){return"array"===x.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?c[y.call(e)]||"object":typeof e},isPlainObject:function(e){var n;if(!e||"object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!v.call(e,"constructor")&&!v.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}if(x.support.ownLast)for(n in e)return v.call(e,n);for(n in e);return n===t||v.call(e,n)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||a;var r=k.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=x.trim(n),n&&E.test(n.replace(A,"@").replace(j,"]").replace(S,"")))?Function("return "+n)():(x.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||x.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&x.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(D,"ms-").replace(L,H)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:b&&!b.call("\ufeff\u00a0")?function(e){return null==e?"":b.call(e)}:function(e){return null==e?"":(e+"").replace(C,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(m)return m.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return d.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),x.isFunction(e)?(r=g.call(arguments,2),i=function(){return e.apply(n||this,r.concat(g.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):t},access:function(e,n,r,i,o,a,s){var l=0,u=e.length,c=null==r;if("object"===x.type(r)){o=!0;for(l in r)x.access(e,n,l,r[l],!0,a,s)}else if(i!==t&&(o=!0,x.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(x(e),n)})),n))for(;u>l;l++)n(e[l],r,s?i:i.call(e[l],l,n(e[l],r)));return o?e:c?n.call(e):u?n(e[0],r):a},now:function(){return(new Date).getTime()},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),x.ready.promise=function(t){if(!n)if(n=x.Deferred(),"complete"===a.readyState)setTimeout(x.ready);else if(a.addEventListener)a.addEventListener("DOMContentLoaded",q,!1),e.addEventListener("load",q,!1);else{a.attachEvent("onreadystatechange",q),e.attachEvent("onload",q);var r=!1;try{r=null==e.frameElement&&a.documentElement}catch(i){}r&&r.doScroll&&function o(){if(!x.isReady){try{r.doScroll("left")}catch(e){return setTimeout(o,50)}_(),x.ready()}}()}return n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){c["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=x(a),function(e,t){var n,r,i,o,a,s,l,u,c,p,f,d,h,g,m,y,v,b="sizzle"+-new Date,w=e.document,T=0,C=0,N=lt(),k=lt(),E=lt(),S=!1,A=function(){return 0},j=typeof t,D=1<<31,L={}.hasOwnProperty,H=[],q=H.pop,_=H.push,M=H.push,O=H.slice,F=H.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},B="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",P="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=R.replace("w","w#"),$="\\["+P+"*("+R+")"+P+"*(?:([*^$|!~]?=)"+P+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+P+"*\\]",I=":("+R+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+P+"+|((?:^|[^\\\\])(?:\\\\.)*)"+P+"+$","g"),X=RegExp("^"+P+"*,"+P+"*"),U=RegExp("^"+P+"*([>+~]|"+P+")"+P+"*"),V=RegExp(P+"*[+~]"),Y=RegExp("="+P+"*([^\\]'\"]*)"+P+"*\\]","g"),J=RegExp(I),G=RegExp("^"+W+"$"),Q={ID:RegExp("^#("+R+")"),CLASS:RegExp("^\\.("+R+")"),TAG:RegExp("^("+R.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:RegExp("^(?:"+B+")$","i"),needsContext:RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,et=/^(?:input|select|textarea|button)$/i,tt=/^h\d$/i,nt=/'|\\/g,rt=RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),it=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{M.apply(H=O.call(w.childNodes),w.childNodes),H[w.childNodes.length].nodeType}catch(ot){M={apply:H.length?function(e,t){_.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function at(e,t,n,i){var o,a,s,l,u,c,d,m,y,x;if((t?t.ownerDocument||t:w)!==f&&p(t),t=t||f,n=n||[],!e||"string"!=typeof e)return n;if(1!==(l=t.nodeType)&&9!==l)return[];if(h&&!i){if(o=Z.exec(e))if(s=o[1]){if(9===l){if(a=t.getElementById(s),!a||!a.parentNode)return n;if(a.id===s)return n.push(a),n}else if(t.ownerDocument&&(a=t.ownerDocument.getElementById(s))&&v(t,a)&&a.id===s)return n.push(a),n}else{if(o[2])return M.apply(n,t.getElementsByTagName(e)),n;if((s=o[3])&&r.getElementsByClassName&&t.getElementsByClassName)return M.apply(n,t.getElementsByClassName(s)),n}if(r.qsa&&(!g||!g.test(e))){if(m=d=b,y=t,x=9===l&&e,1===l&&"object"!==t.nodeName.toLowerCase()){c=bt(e),(d=t.getAttribute("id"))?m=d.replace(nt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",u=c.length;while(u--)c[u]=m+xt(c[u]);y=V.test(e)&&t.parentNode||t,x=c.join(",")}if(x)try{return M.apply(n,y.querySelectorAll(x)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return At(e.replace(z,"$1"),t,n,i)}function st(e){return K.test(e+"")}function lt(){var e=[];function t(n,r){return e.push(n+=" ")>o.cacheLength&&delete t[e.shift()],t[n]=r}return t}function ut(e){return e[b]=!0,e}function ct(e){var t=f.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function pt(e,t,n){e=e.split("|");var r,i=e.length,a=n?null:t;while(i--)(r=o.attrHandle[e[i]])&&r!==t||(o.attrHandle[e[i]]=a)}function ft(e,t){var n=e.getAttributeNode(t);return n&&n.specified?n.value:e[t]===!0?t.toLowerCase():null}function dt(e,t){return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}function ht(e){return"input"===e.nodeName.toLowerCase()?e.defaultValue:t}function gt(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function mt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function yt(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function vt(e){return ut(function(t){return t=+t,ut(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}s=at.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},r=at.support={},p=at.setDocument=function(e){var n=e?e.ownerDocument||e:w,i=n.parentWindow;return n!==f&&9===n.nodeType&&n.documentElement?(f=n,d=n.documentElement,h=!s(n),i&&i.frameElement&&i.attachEvent("onbeforeunload",function(){p()}),r.attributes=ct(function(e){return e.innerHTML="<a href='#'></a>",pt("type|href|height|width",dt,"#"===e.firstChild.getAttribute("href")),pt(B,ft,null==e.getAttribute("disabled")),e.className="i",!e.getAttribute("className")}),r.input=ct(function(e){return e.innerHTML="<input>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")}),pt("value",ht,r.attributes&&r.input),r.getElementsByTagName=ct(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),r.getElementsByClassName=ct(function(e){return e.innerHTML="<div class='a'></div><div class='a i'></div>",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),r.getById=ct(function(e){return d.appendChild(e).id=b,!n.getElementsByName||!n.getElementsByName(b).length}),r.getById?(o.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){return e.getAttribute("id")===t}}):(delete o.find.ID,o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),o.find.TAG=r.getElementsByTagName?function(e,n){return typeof n.getElementsByTagName!==j?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},o.find.CLASS=r.getElementsByClassName&&function(e,n){return typeof n.getElementsByClassName!==j&&h?n.getElementsByClassName(e):t},m=[],g=[],(r.qsa=st(n.querySelectorAll))&&(ct(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||g.push("\\["+P+"*(?:value|"+B+")"),e.querySelectorAll(":checked").length||g.push(":checked")}),ct(function(e){var t=n.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&g.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(r.matchesSelector=st(y=d.webkitMatchesSelector||d.mozMatchesSelector||d.oMatchesSelector||d.msMatchesSelector))&&ct(function(e){r.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),m.push("!=",I)}),g=g.length&&RegExp(g.join("|")),m=m.length&&RegExp(m.join("|")),v=st(d.contains)||d.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},r.sortDetached=ct(function(e){return 1&e.compareDocumentPosition(n.createElement("div"))}),A=d.compareDocumentPosition?function(e,t){if(e===t)return S=!0,0;var i=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t);return i?1&i||!r.sortDetached&&t.compareDocumentPosition(e)===i?e===n||v(w,e)?-1:t===n||v(w,t)?1:c?F.call(c,e)-F.call(c,t):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return S=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:c?F.call(c,e)-F.call(c,t):0;if(o===a)return gt(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?gt(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},n):f},at.matches=function(e,t){return at(e,null,null,t)},at.matchesSelector=function(e,t){if((e.ownerDocument||e)!==f&&p(e),t=t.replace(Y,"='$1']"),!(!r.matchesSelector||!h||m&&m.test(t)||g&&g.test(t)))try{var n=y.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(i){}return at(t,f,null,[e]).length>0},at.contains=function(e,t){return(e.ownerDocument||e)!==f&&p(e),v(e,t)},at.attr=function(e,n){(e.ownerDocument||e)!==f&&p(e);var i=o.attrHandle[n.toLowerCase()],a=i&&L.call(o.attrHandle,n.toLowerCase())?i(e,n,!h):t;return a===t?r.attributes||!h?e.getAttribute(n):(a=e.getAttributeNode(n))&&a.specified?a.value:null:a},at.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},at.uniqueSort=function(e){var t,n=[],i=0,o=0;if(S=!r.detectDuplicates,c=!r.sortStable&&e.slice(0),e.sort(A),S){while(t=e[o++])t===e[o]&&(i=n.push(o));while(i--)e.splice(n[i],1)}return e},a=at.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=a(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=a(t);return n},o=at.selectors={cacheLength:50,createPseudo:ut,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(rt,it),e[3]=(e[4]||e[5]||"").replace(rt,it),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||at.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&at.error(e[0]),e},PSEUDO:function(e){var n,r=!e[5]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]&&e[4]!==t?e[2]=e[4]:r&&J.test(r)&&(n=bt(r,!0))&&(n=r.indexOf(")",r.length-n)-r.length)&&(e[0]=e[0].slice(0,n),e[2]=r.slice(0,n)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(rt,it).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=N[e+" "];return t||(t=RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&N(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=at.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,l){var u,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!l&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[b]||(m[b]={}),u=c[e]||[],d=u[0]===T&&u[1],f=u[0]===T&&u[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[T,d,f];break}}else if(v&&(u=(t[b]||(t[b]={}))[e])&&u[0]===T)f=u[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[b]||(p[b]={}))[e]=[T,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=o.pseudos[e]||o.setFilters[e.toLowerCase()]||at.error("unsupported pseudo: "+e);return r[b]?r(t):r.length>1?(n=[e,e,"",t],o.setFilters.hasOwnProperty(e.toLowerCase())?ut(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=F.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:ut(function(e){var t=[],n=[],r=l(e.replace(z,"$1"));return r[b]?ut(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:ut(function(e){return function(t){return at(e,t).length>0}}),contains:ut(function(e){return function(t){return(t.textContent||t.innerText||a(t)).indexOf(e)>-1}}),lang:ut(function(e){return G.test(e||"")||at.error("unsupported lang: "+e),e=e.replace(rt,it).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===d},focus:function(e){return e===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!o.pseudos.empty(e)},header:function(e){return tt.test(e.nodeName)},input:function(e){return et.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:vt(function(){return[0]}),last:vt(function(e,t){return[t-1]}),eq:vt(function(e,t,n){return[0>n?n+t:n]}),even:vt(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:vt(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:vt(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:vt(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}};for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})o.pseudos[n]=mt(n);for(n in{submit:!0,reset:!0})o.pseudos[n]=yt(n);function bt(e,t){var n,r,i,a,s,l,u,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,l=[],u=o.preFilter;while(s){(!n||(r=X.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),l.push(i=[])),n=!1,(r=U.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(z," ")}),s=s.slice(n.length));for(a in o.filter)!(r=Q[a].exec(s))||u[a]&&!(r=u[a](r))||(n=r.shift(),i.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?at.error(e):k(e,l).slice(0)}function xt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function wt(e,t,n){var r=t.dir,o=n&&"parentNode"===r,a=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||o)return e(t,n,i)}:function(t,n,s){var l,u,c,p=T+" "+a;if(s){while(t=t[r])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[r])if(1===t.nodeType||o)if(c=t[b]||(t[b]={}),(u=c[r])&&u[0]===p){if((l=u[1])===!0||l===i)return l===!0}else if(u=c[r]=[p],u[1]=e(t,n,s)||i,u[1]===!0)return!0}}function Tt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function Ct(e,t,n,r,i){var o,a=[],s=0,l=e.length,u=null!=t;for(;l>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),u&&t.push(s));return a}function Nt(e,t,n,r,i,o){return r&&!r[b]&&(r=Nt(r)),i&&!i[b]&&(i=Nt(i,o)),ut(function(o,a,s,l){var u,c,p,f=[],d=[],h=a.length,g=o||St(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:Ct(g,f,e,s,l),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,l),r){u=Ct(y,d),r(u,[],s,l),c=u.length;while(c--)(p=u[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){u=[],c=y.length;while(c--)(p=y[c])&&u.push(m[c]=p);i(null,y=[],u,l)}c=y.length;while(c--)(p=y[c])&&(u=i?F.call(o,p):f[c])>-1&&(o[u]=!(a[u]=p))}}else y=Ct(y===a?y.splice(h,y.length):y),i?i(null,a,y,l):M.apply(a,y)})}function kt(e){var t,n,r,i=e.length,a=o.relative[e[0].type],s=a||o.relative[" "],l=a?1:0,c=wt(function(e){return e===t},s,!0),p=wt(function(e){return F.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;i>l;l++)if(n=o.relative[e[l].type])f=[wt(Tt(f),n)];else{if(n=o.filter[e[l].type].apply(null,e[l].matches),n[b]){for(r=++l;i>r;r++)if(o.relative[e[r].type])break;return Nt(l>1&&Tt(f),l>1&&xt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&kt(e.slice(l,r)),i>r&&kt(e=e.slice(r)),i>r&&xt(e))}f.push(n)}return Tt(f)}function Et(e,t){var n=0,r=t.length>0,a=e.length>0,s=function(s,l,c,p,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,C=u,N=s||a&&o.find.TAG("*",d&&l.parentNode||l),k=T+=null==C?1:Math.random()||.1;for(w&&(u=l!==f&&l,i=n);null!=(h=N[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,l,c)){p.push(h);break}w&&(T=k,i=++n)}r&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,r&&b!==v){g=0;while(m=t[g++])m(x,y,l,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=q.call(p));y=Ct(y)}M.apply(p,y),w&&!s&&y.length>0&&v+t.length>1&&at.uniqueSort(p)}return w&&(T=k,u=C),x};return r?ut(s):s}l=at.compile=function(e,t){var n,r=[],i=[],o=E[e+" "];if(!o){t||(t=bt(e)),n=t.length;while(n--)o=kt(t[n]),o[b]?r.push(o):i.push(o);o=E(e,Et(i,r))}return o};function St(e,t,n){var r=0,i=t.length;for(;i>r;r++)at(e,t[r],n);return n}function At(e,t,n,i){var a,s,u,c,p,f=bt(e);if(!i&&1===f.length){if(s=f[0]=f[0].slice(0),s.length>2&&"ID"===(u=s[0]).type&&r.getById&&9===t.nodeType&&h&&o.relative[s[1].type]){if(t=(o.find.ID(u.matches[0].replace(rt,it),t)||[])[0],!t)return n;e=e.slice(s.shift().value.length)}a=Q.needsContext.test(e)?0:s.length;while(a--){if(u=s[a],o.relative[c=u.type])break;if((p=o.find[c])&&(i=p(u.matches[0].replace(rt,it),V.test(s[0].type)&&t.parentNode||t))){if(s.splice(a,1),e=i.length&&xt(s),!e)return M.apply(n,i),n;break}}}return l(e,f)(i,t,!h,n,V.test(e)),n}o.pseudos.nth=o.pseudos.eq;function jt(){}jt.prototype=o.filters=o.pseudos,o.setFilters=new jt,r.sortStable=b.split("").sort(A).join("")===b,p(),[0,0].sort(A),r.detectDuplicates=S,x.find=at,x.expr=at.selectors,x.expr[":"]=x.expr.pseudos,x.unique=at.uniqueSort,x.text=at.getText,x.isXMLDoc=at.isXML,x.contains=at.contains}(e);var O={};function F(e){var t=O[e]={};return x.each(e.match(T)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?O[e]||F(e):x.extend({},e);var n,r,i,o,a,s,l=[],u=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=l.length,n=!0;l&&o>a;a++)if(l[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,l&&(u?u.length&&c(u.shift()):r?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function i(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=l.length:r&&(s=t,c(r))}return this},remove:function(){return l&&x.each(arguments,function(e,t){var r;while((r=x.inArray(t,l,r))>-1)l.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?x.inArray(e,l)>-1:!(!l||!l.length)},empty:function(){return l=[],o=0,this},disable:function(){return l=u=r=t,this},disabled:function(){return!l},lock:function(){return u=t,r||p.disable(),this},locked:function(){return!u},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],!l||i&&!u||(n?u.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var a=o[0],s=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=g.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?g.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,l,u;if(r>1)for(s=Array(r),l=Array(r),u=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(a(t,u,n)).fail(o.reject).progress(a(t,l,s)):--i;return i||o.resolveWith(u,n),o.promise()}}),x.support=function(t){var n,r,o,s,l,u,c,p,f,d=a.createElement("div");if(d.setAttribute("className","t"),d.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",n=d.getElementsByTagName("*")||[],r=d.getElementsByTagName("a")[0],!r||!r.style||!n.length)return t;s=a.createElement("select"),u=s.appendChild(a.createElement("option")),o=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t.getSetAttribute="t"!==d.className,t.leadingWhitespace=3===d.firstChild.nodeType,t.tbody=!d.getElementsByTagName("tbody").length,t.htmlSerialize=!!d.getElementsByTagName("link").length,t.style=/top/.test(r.getAttribute("style")),t.hrefNormalized="/a"===r.getAttribute("href"),t.opacity=/^0.5/.test(r.style.opacity),t.cssFloat=!!r.style.cssFloat,t.checkOn=!!o.value,t.optSelected=u.selected,t.enctype=!!a.createElement("form").enctype,t.html5Clone="<:nav></:nav>"!==a.createElement("nav").cloneNode(!0).outerHTML,t.inlineBlockNeedsLayout=!1,t.shrinkWrapBlocks=!1,t.pixelPosition=!1,t.deleteExpando=!0,t.noCloneEvent=!0,t.reliableMarginRight=!0,t.boxSizingReliable=!0,o.checked=!0,t.noCloneChecked=o.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!u.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}o=a.createElement("input"),o.setAttribute("value",""),t.input=""===o.getAttribute("value"),o.value="t",o.setAttribute("type","radio"),t.radioValue="t"===o.value,o.setAttribute("checked","t"),o.setAttribute("name","t"),l=a.createDocumentFragment(),l.appendChild(o),t.appendChecked=o.checked,t.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip;for(f in x(t))break;return t.ownLast="0"!==f,x(function(){var n,r,o,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",l=a.getElementsByTagName("body")[0];l&&(n=a.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",l.appendChild(n).appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",o=d.getElementsByTagName("td"),o[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===o[0].offsetHeight,o[0].style.display="",o[1].style.display="none",t.reliableHiddenOffsets=p&&0===o[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",x.swap(l,null!=l.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===d.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(a.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="<div></div>",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(l.style.zoom=1)),l.removeChild(n),n=d=o=r=null) +}),n=s=l=u=r=o=null,t}({});var B=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;function R(e,n,r,i){if(x.acceptData(e)){var o,a,s=x.expando,l=e.nodeType,u=l?x.cache:e,c=l?e[s]:e[s]&&s;if(c&&u[c]&&(i||u[c].data)||r!==t||"string"!=typeof n)return c||(c=l?e[s]=p.pop()||x.guid++:s),u[c]||(u[c]=l?{}:{toJSON:x.noop}),("object"==typeof n||"function"==typeof n)&&(i?u[c]=x.extend(u[c],n):u[c].data=x.extend(u[c].data,n)),a=u[c],i||(a.data||(a.data={}),a=a.data),r!==t&&(a[x.camelCase(n)]=r),"string"==typeof n?(o=a[n],null==o&&(o=a[x.camelCase(n)])):o=a,o}}function W(e,t,n){if(x.acceptData(e)){var r,i,o=e.nodeType,a=o?x.cache:e,s=o?e[x.expando]:x.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){x.isArray(t)?t=t.concat(x.map(t,x.camelCase)):t in r?t=[t]:(t=x.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;while(i--)delete r[t[i]];if(n?!I(r):!x.isEmptyObject(r))return}(n||(delete a[s].data,I(a[s])))&&(o?x.cleanData([e],!0):x.support.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}x.extend({cache:{},noData:{applet:!0,embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(e){return e=e.nodeType?x.cache[e[x.expando]]:e[x.expando],!!e&&!I(e)},data:function(e,t,n){return R(e,t,n)},removeData:function(e,t){return W(e,t)},_data:function(e,t,n){return R(e,t,n,!0)},_removeData:function(e,t){return W(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&x.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),x.fn.extend({data:function(e,n){var r,i,o=null,a=0,s=this[0];if(e===t){if(this.length&&(o=x.data(s),1===s.nodeType&&!x._data(s,"parsedAttrs"))){for(r=s.attributes;r.length>a;a++)i=r[a].name,0===i.indexOf("data-")&&(i=x.camelCase(i.slice(5)),$(s,i,o[i]));x._data(s,"parsedAttrs",!0)}return o}return"object"==typeof e?this.each(function(){x.data(this,e)}):arguments.length>1?this.each(function(){x.data(this,e,n)}):s?$(s,e,x.data(s,e)):null},removeData:function(e){return this.each(function(){x.removeData(this,e)})}});function $(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(P,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:B.test(r)?x.parseJSON(r):r}catch(o){}x.data(e,n,r)}else r=t}return r}function I(e){var t;for(t in e)if(("data"!==t||!x.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}x.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=x._data(e,n),r&&(!i||x.isArray(r)?i=x._data(e,n,x.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),a=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return x._data(e,n)||x._data(e,n,{empty:x.Callbacks("once memory").add(function(){x._removeData(e,t+"queue"),x._removeData(e,n)})})}}),x.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?x.queue(this[0],e):n===t?this:this.each(function(){var t=x.queue(this,e,n);x._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=x.Deferred(),a=this,s=this.length,l=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=x._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(l));return l(),o.promise(n)}});var z,X,U=/[\t\r\n\f]/g,V=/\r/g,Y=/^(?:input|select|textarea|button|object)$/i,J=/^(?:a|area)$/i,G=/^(?:checked|selected)$/i,Q=x.support.getSetAttribute,K=x.support.input;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return e=x.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,l="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,l=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e,r="boolean"==typeof t;return x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var o,a=0,s=x(this),l=t,u=e.match(T)||[];while(o=u[a++])l=r?l:!s.hasClass(o),s[l?"addClass":"removeClass"](o)}else(n===i||"boolean"===n)&&(this.className&&x._data(this,"__className__",this.className),this.className=this.className||e===!1?"":x._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(U," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=x.isFunction(e),this.each(function(n){var o;1===this.nodeType&&(o=i?e.call(this,n,x(this).val()):e,null==o?o="":"number"==typeof o?o+="":x.isArray(o)&&(o=x.map(o,function(e){return null==e?"":e+""})),r=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=x.valHooks[o.type]||x.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(V,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=x.find.attr(e,"value");return null!=t?t:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,l=0>i?s:o?i:0;for(;s>l;l++)if(n=r[l],!(!n.selected&&l!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),a=i.length;while(a--)r=i[a],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,n,r){var o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===i?x.prop(e,n,r):(1===s&&x.isXMLDoc(e)||(n=n.toLowerCase(),o=x.attrHooks[n]||(x.expr.match.bool.test(n)?X:z)),r===t?o&&"get"in o&&null!==(a=o.get(e,n))?a:(a=x.find.attr(e,n),null==a?t:a):null!==r?o&&"set"in o&&(a=o.set(e,r,n))!==t?a:(e.setAttribute(n,r+""),r):(x.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(T);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)?K&&Q||!G.test(n)?e[r]=!1:e[x.camelCase("default-"+n)]=e[r]=!1:x.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!x.isXMLDoc(e),a&&(n=x.propFix[n]||n,o=x.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var t=x.find.attr(e,"tabindex");return t?parseInt(t,10):Y.test(e.nodeName)||J.test(e.nodeName)&&e.href?0:-1}}}}),X={set:function(e,t,n){return t===!1?x.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&x.propFix[n]||n,n):e[x.camelCase("default-"+n)]=e[n]=!0,n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,n){var r=x.expr.attrHandle[n]||x.find.attr;x.expr.attrHandle[n]=K&&Q||!G.test(n)?function(e,n,i){var o=x.expr.attrHandle[n],a=i?t:(x.expr.attrHandle[n]=t)!=r(e,n,i)?n.toLowerCase():null;return x.expr.attrHandle[n]=o,a}:function(e,n,r){return r?t:e[x.camelCase("default-"+n)]?n.toLowerCase():null}}),K&&Q||(x.attrHooks.value={set:function(e,n,r){return x.nodeName(e,"input")?(e.defaultValue=n,t):z&&z.set(e,n,r)}}),Q||(z={set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},x.expr.attrHandle.id=x.expr.attrHandle.name=x.expr.attrHandle.coords=function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&""!==i.value?i.value:null},x.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&r.specified?r.value:t},set:z.set},x.attrHooks.contenteditable={set:function(e,t,n){z.set(e,""===t?!1:t,n)}},x.each(["width","height"],function(e,n){x.attrHooks[n]={set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}}})),x.support.hrefNormalized||x.each(["href","src"],function(e,t){x.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),x.support.style||(x.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.support.enctype||(x.propFix.enctype="encoding"),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,n){return x.isArray(n)?e.checked=x.inArray(x(e).val(),n)>=0:t}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}function at(){try{return a.activeElement}catch(e){}}x.event={global:{},add:function(e,n,r,o,a){var s,l,u,c,p,f,d,h,g,m,y,v=x._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=x.guid++),(l=v.events)||(l=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof x===i||e&&x.event.triggered===e.type?t:x.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(T)||[""],u=n.length;while(u--)s=rt.exec(n[u])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),g&&(p=x.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=x.event.special[g]||{},d=x.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&x.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=l[g])||(h=l[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),x.event.global[g]=!0);e=null}},remove:function(e,t,n,r,i){var o,a,s,l,u,c,p,f,d,h,g,m=x.hasData(e)&&x._data(e);if(m&&(c=m.events)){t=(t||"").match(T)||[""],u=t.length;while(u--)if(s=rt.exec(t[u])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=x.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),l=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));l&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||x.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)x.event.remove(e,d+t[u],n,r,!0);x.isEmptyObject(c)&&(delete m.handle,x._removeData(e,"events"))}},trigger:function(n,r,i,o){var s,l,u,c,p,f,d,h=[i||a],g=v.call(n,"type")?n.type:n,m=v.call(n,"namespace")?n.namespace.split("."):[];if(u=f=i=i||a,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+x.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),l=0>g.indexOf(":")&&"on"+g,n=n[x.expando]?n:new x.Event(g,"object"==typeof n&&n),n.isTrigger=o?2:3,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:x.makeArray(r,[n]),p=x.event.special[g]||{},o||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!o&&!p.noBubble&&!x.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(u=u.parentNode);u;u=u.parentNode)h.push(u),f=u;f===(i.ownerDocument||a)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((u=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(x._data(u,"events")||{})[n.type]&&x._data(u,"handle"),s&&s.apply(u,r),s=l&&u[l],s&&x.acceptData(u)&&s.apply&&s.apply(u,r)===!1&&n.preventDefault();if(n.type=g,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(h.pop(),r)===!1)&&x.acceptData(i)&&l&&i[g]&&!x.isWindow(i)){f=i[l],f&&(i[l]=null),x.event.triggered=g;try{i[g]()}catch(y){}x.event.triggered=t,f&&(i[l]=f)}return n.result}},dispatch:function(e){e=x.event.fix(e);var n,r,i,o,a,s=[],l=g.call(arguments),u=(x._data(this,"events")||{})[e.type]||[],c=x.event.special[e.type]||{};if(l[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((x.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,l),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],l=n.delegateCount,u=e.target;if(l&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!=this;u=u.parentNode||this)if(1===u.nodeType&&(u.disabled!==!0||"click"!==e.type)){for(o=[],a=0;l>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?x(r,this).index(u)>=0:x.find(r,this,null,[u]).length),o[r]&&o.push(i);o.length&&s.push({elem:u,handlers:o})}return n.length>l&&s.push({elem:this,handlers:n.slice(l)}),s},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return e.target||(e.target=o.srcElement||a),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,o):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,o,s=n.button,l=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||a,o=i.documentElement,r=i.body,e.pageX=n.clientX+(o&&o.scrollLeft||r&&r.scrollLeft||0)-(o&&o.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(o&&o.scrollTop||r&&r.scrollTop||0)-(o&&o.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&l&&(e.relatedTarget=l===e.target?n.toElement:l),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==at()&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===at()&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},click:{trigger:function(){return x.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=a.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},x.Event=function(e,n){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&x.extend(this,n),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,t):new x.Event(e,n)},x.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.submitBubbles||(x.event.special.submit={setup:function(){return x.nodeName(this,"form")?!1:(x.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=x.nodeName(n,"input")||x.nodeName(n,"button")?n.form:t;r&&!x._data(r,"submitBubbles")&&(x.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),x._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&x.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return x.nodeName(this,"form")?!1:(x.event.remove(this,"._submit"),t)}}),x.support.changeBubbles||(x.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(x.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),x.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),x.event.simulate("change",this,e,!0)})),!1):(x.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!x._data(t,"changeBubbles")&&(x.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||x.event.simulate("change",this.parentNode,e,!0)}),x._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return x.event.remove(this,"._change"),!Z.test(this.nodeName)}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&a.addEventListener(e,r,!0)},teardown:function(){0===--n&&a.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return x().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=x.guid++)),this.each(function(){x.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,x(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){x.event.remove(this,e,r,n)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?x.event.trigger(e,n,r,!0):t}});var st=/^.[^:#\[\.,]*$/,lt=/^(?:parents|prev(?:Until|All))/,ut=x.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t,n=x(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(x.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e||[],!0))},filter:function(e){return this.pushStack(ft(this,e||[],!1))},is:function(e){return!!ft(this,"string"==typeof e&&ut.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],a=ut.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(a?a.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?x.inArray(this[0],x(e)):x.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(ct[e]||(i=x.unique(i)),lt.test(e)&&(i=i.reverse())),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!x(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(st.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return x.inArray(e,t)>=0!==n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/<tbody/i,wt=/<|&#?\w+;/,Tt=/<(?:script|style|link)/i,Ct=/^(?:checkbox|radio)$/i,Nt=/checked\s*(?:[^=]|=\s*.checked.)/i,kt=/^$|\/(?:java|ecma)script/i,Et=/^true\/(.*)/,St=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,At={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:x.support.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},jt=dt(a),Dt=jt.appendChild(a.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===t?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||a).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(Ft(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&_t(Ft(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&x.cleanData(Ft(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&x.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!x.support.htmlSerialize&&mt.test(e)||!x.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1></$2>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(x.cleanData(Ft(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=d.apply([],e);var r,i,o,a,s,l,u=0,c=this.length,p=this,f=c-1,h=e[0],g=x.isFunction(h);if(g||!(1>=c||"string"!=typeof h||x.support.checkClone)&&Nt.test(h))return this.each(function(r){var i=p.eq(r);g&&(e[0]=h.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(l=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),r=l.firstChild,1===l.childNodes.length&&(l=r),r)){for(a=x.map(Ft(l,"script"),Ht),o=a.length;c>u;u++)i=l,u!==f&&(i=x.clone(i,!0,!0),o&&x.merge(a,Ft(i,"script"))),t.call(this[u],i,u);if(o)for(s=a[a.length-1].ownerDocument,x.map(a,qt),u=0;o>u;u++)i=a[u],kt.test(i.type||"")&&!x._data(i,"globalEval")&&x.contains(s,i)&&(i.src?x._evalUrl(i.src):x.globalEval((i.text||i.textContent||i.innerHTML||"").replace(St,"")));l=r=null}return this}});function Lt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function Ht(e){return e.type=(null!==x.find.attr(e,"type"))+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function _t(e,t){var n,r=0;for(;null!=(n=e[r]);r++)x._data(n,"globalEval",!t||x._data(t[r],"globalEval"))}function Mt(e,t){if(1===t.nodeType&&x.hasData(e)){var n,r,i,o=x._data(e),a=x._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)x.event.add(t,n,s[n][r])}a.data&&(a.data=x.extend({},a.data))}}function Ot(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!x.support.noCloneEvent&&t[x.expando]){i=x._data(t);for(r in i.events)x.removeEvent(t,r,i.handle);t.removeAttribute(x.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),x.support.html5Clone&&e.innerHTML&&!x.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Ct.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=0,i=[],o=x(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),x(o[r])[t](n),h.apply(i,n.get());return this.pushStack(i)}});function Ft(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||x.nodeName(o,n)?s.push(o):x.merge(s,Ft(o,n));return n===t||n&&x.nodeName(e,n)?x.merge([e],s):s}function Bt(e){Ct.test(e.type)&&(e.defaultChecked=e.checked)}x.extend({clone:function(e,t,n){var r,i,o,a,s,l=x.contains(e.ownerDocument,e);if(x.support.html5Clone||x.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(x.support.noCloneEvent&&x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(r=Ft(o),s=Ft(e),a=0;null!=(i=s[a]);++a)r[a]&&Ot(i,r[a]);if(t)if(n)for(s=s||Ft(e),r=r||Ft(o),a=0;null!=(i=s[a]);a++)Mt(i,r[a]);else Mt(e,o);return r=Ft(o,"script"),r.length>0&&_t(r,!l&&Ft(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,l,u,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===x.type(o))x.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),l=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[l]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1></$2>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!x.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!x.support.tbody){o="table"!==l||xt.test(o)?"<table>"!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)x.nodeName(u=o.childNodes[i],"tbody")&&!u.childNodes.length&&o.removeChild(u)}x.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),x.support.appendChecked||x.grep(Ft(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===x.inArray(o,r))&&(a=x.contains(o.ownerDocument,o),s=Ft(f.appendChild(o),"script"),a&&_t(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,l=x.expando,u=x.cache,c=x.support.deleteExpando,f=x.event.special;for(;null!=(n=e[s]);s++)if((t||x.acceptData(n))&&(o=n[l],a=o&&u[o])){if(a.events)for(r in a.events)f[r]?x.event.remove(n,r):x.removeEvent(n,r,a.handle); + u[o]&&(delete u[o],c?delete n[l]:typeof n.removeAttribute!==i?n.removeAttribute(l):n[l]=null,p.push(o))}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}}),x.fn.extend({wrapAll:function(e){if(x.isFunction(e))return this.each(function(t){x(this).wrapAll(e.call(this,t))});if(this[0]){var t=x(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+w+")(.*)$","i"),Yt=RegExp("^("+w+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+w+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=x._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=x._data(r,"olddisplay",ln(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&x._data(r,"olddisplay",i?n:x.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}x.fn.extend({css:function(e,n){return x.access(this,function(e,n,r){var i,o,a={},s=0;if(x.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=x.css(e,n[s],!1,o);return a}return r!==t?x.style(e,n,r):x.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){var t="boolean"==typeof e;return this.each(function(){(t?e:nn(this))?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":x.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,l=x.camelCase(n),u=e.style;if(n=x.cssProps[l]||(x.cssProps[l]=tn(u,l)),s=x.cssHooks[n]||x.cssHooks[l],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:u[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(x.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||x.cssNumber[l]||(r+="px"),x.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(u[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{u[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,l=x.camelCase(n);return n=x.cssProps[l]||(x.cssProps[l]=tn(e.style,l)),s=x.cssHooks[n]||x.cssHooks[l],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||x.isNumeric(o)?o||0:a):a}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s.getPropertyValue(n)||s[n]:t,u=e.style;return s&&(""!==l||x.contains(e.ownerDocument,e)||(l=x.style(e,n)),Yt.test(l)&&Ut.test(n)&&(i=u.width,o=u.minWidth,a=u.maxWidth,u.minWidth=u.maxWidth=u.width=l,l=s.width,u.width=i,u.minWidth=o,u.maxWidth=a)),l}):a.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s[n]:t,u=e.style;return null==l&&u&&u[n]&&(l=u[n]),Yt.test(l)&&!zt.test(n)&&(i=u.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),u.left="fontSize"===n?"1em":l,l=u.pixelLeft+"px",u.left=i,a&&(o.left=a)),""===l?"auto":l});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=x.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=x.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=x.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=x.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=x.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function ln(e){var t=a,n=Gt[e];return n||(n=un(e,t),"none"!==n&&n||(Pt=(Pt||x("<iframe frameborder='0' width='0' height='0'/>").css("cssText","display:block !important")).appendTo(t.documentElement),t=(Pt[0].contentWindow||Pt[0].contentDocument).document,t.write("<!doctype html><html><body>"),t.close(),n=un(e,t),Pt.detach()),Gt[e]=n),n}function un(e,t){var n=x(t.createElement(e)).appendTo(t.body),r=x.css(n[0],"display");return n.remove(),r}x.each(["height","width"],function(e,n){x.cssHooks[n]={get:function(e,r,i){return r?0===e.offsetWidth&&Xt.test(x.css(e,"display"))?x.swap(e,Qt,function(){return sn(e,n,i)}):sn(e,n,i):t},set:function(e,t,r){var i=r&&Rt(e);return on(e,t,r?an(e,n,r,x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,i),i):0)}}}),x.support.opacity||(x.cssHooks.opacity={get:function(e,t){return It.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=x.isNumeric(t)?"alpha(opacity="+100*t+")":"",o=r&&r.filter||n.filter||"";n.zoom=1,(t>=1||""===t)&&""===x.trim(o.replace($t,""))&&n.removeAttribute&&(n.removeAttribute("filter"),""===t||r&&!r.filter)||(n.filter=$t.test(o)?o.replace($t,i):o+" "+i)}}),x(function(){x.support.reliableMarginRight||(x.cssHooks.marginRight={get:function(e,n){return n?x.swap(e,{display:"inline-block"},Wt,[e,"marginRight"]):t}}),!x.support.pixelPosition&&x.fn.position&&x.each(["top","left"],function(e,n){x.cssHooks[n]={get:function(e,r){return r?(r=Wt(e,n),Yt.test(r)?x(e).position()[n]+"px":r):t}}})}),x.expr&&x.expr.filters&&(x.expr.filters.hidden=function(e){return 0>=e.offsetWidth&&0>=e.offsetHeight||!x.support.reliableHiddenOffsets&&"none"===(e.style&&e.style.display||x.css(e,"display"))},x.expr.filters.visible=function(e){return!x.expr.filters.hidden(e)}),x.each({margin:"",padding:"",border:"Width"},function(e,t){x.cssHooks[e+t]={expand:function(n){var r=0,i={},o="string"==typeof n?n.split(" "):[n];for(;4>r;r++)i[e+Zt[r]+t]=o[r]||o[r-2]||o[0];return i}},Ut.test(e)||(x.cssHooks[e+t].set=on)});var cn=/%20/g,pn=/\[\]$/,fn=/\r?\n/g,dn=/^(?:submit|button|image|reset|file)$/i,hn=/^(?:input|select|textarea|keygen)/i;x.fn.extend({serialize:function(){return x.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=x.prop(this,"elements");return e?x.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!x(this).is(":disabled")&&hn.test(this.nodeName)&&!dn.test(e)&&(this.checked||!Ct.test(e))}).map(function(e,t){var n=x(this).val();return null==n?null:x.isArray(n)?x.map(n,function(e){return{name:t.name,value:e.replace(fn,"\r\n")}}):{name:t.name,value:n.replace(fn,"\r\n")}}).get()}}),x.param=function(e,n){var r,i=[],o=function(e,t){t=x.isFunction(t)?t():null==t?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(n===t&&(n=x.ajaxSettings&&x.ajaxSettings.traditional),x.isArray(e)||e.jquery&&!x.isPlainObject(e))x.each(e,function(){o(this.name,this.value)});else for(r in e)gn(r,e[r],n,o);return i.join("&").replace(cn,"+")};function gn(e,t,n,r){var i;if(x.isArray(t))x.each(t,function(t,i){n||pn.test(e)?r(e,i):gn(e+"["+("object"==typeof i?t:"")+"]",i,n,r)});else if(n||"object"!==x.type(t))r(e,t);else for(i in t)gn(e+"["+i+"]",t[i],n,r)}x.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){x.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),x.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)}});var mn,yn,vn=x.now(),bn=/\?/,xn=/#.*$/,wn=/([?&])_=[^&]*/,Tn=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Cn=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Nn=/^(?:GET|HEAD)$/,kn=/^\/\//,En=/^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,Sn=x.fn.load,An={},jn={},Dn="*/".concat("*");try{yn=o.href}catch(Ln){yn=a.createElement("a"),yn.href="",yn=yn.href}mn=En.exec(yn.toLowerCase())||[];function Hn(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(T)||[];if(x.isFunction(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function qn(e,n,r,i){var o={},a=e===jn;function s(l){var u;return o[l]=!0,x.each(e[l]||[],function(e,l){var c=l(n,r,i);return"string"!=typeof c||a||o[c]?a?!(u=c):t:(n.dataTypes.unshift(c),s(c),!1)}),u}return s(n.dataTypes[0])||!o["*"]&&s("*")}function _n(e,n){var r,i,o=x.ajaxSettings.flatOptions||{};for(i in n)n[i]!==t&&((o[i]?e:r||(r={}))[i]=n[i]);return r&&x.extend(!0,e,r),e}x.fn.load=function(e,n,r){if("string"!=typeof e&&Sn)return Sn.apply(this,arguments);var i,o,a,s=this,l=e.indexOf(" ");return l>=0&&(i=e.slice(l,e.length),e=e.slice(0,l)),x.isFunction(n)?(r=n,n=t):n&&"object"==typeof n&&(a="POST"),s.length>0&&x.ajax({url:e,type:a,dataType:"html",data:n}).done(function(e){o=arguments,s.html(i?x("<div>").append(x.parseHTML(e)).find(i):e)}).complete(r&&function(e,t){s.each(r,o||[e.responseText,t,e])}),this},x.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){x.fn[t]=function(e){return this.on(t,e)}}),x.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:yn,type:"GET",isLocal:Cn.test(mn[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Dn,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":x.parseJSON,"text xml":x.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?_n(_n(e,x.ajaxSettings),t):_n(x.ajaxSettings,e)},ajaxPrefilter:Hn(An),ajaxTransport:Hn(jn),ajax:function(e,n){"object"==typeof e&&(n=e,e=t),n=n||{};var r,i,o,a,s,l,u,c,p=x.ajaxSetup({},n),f=p.context||p,d=p.context&&(f.nodeType||f.jquery)?x(f):x.event,h=x.Deferred(),g=x.Callbacks("once memory"),m=p.statusCode||{},y={},v={},b=0,w="canceled",C={readyState:0,getResponseHeader:function(e){var t;if(2===b){if(!c){c={};while(t=Tn.exec(a))c[t[1].toLowerCase()]=t[2]}t=c[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===b?a:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return b||(e=v[n]=v[n]||e,y[e]=t),this},overrideMimeType:function(e){return b||(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>b)for(t in e)m[t]=[m[t],e[t]];else C.always(e[C.status]);return this},abort:function(e){var t=e||w;return u&&u.abort(t),k(0,t),this}};if(h.promise(C).complete=g.add,C.success=C.done,C.error=C.fail,p.url=((e||p.url||yn)+"").replace(xn,"").replace(kn,mn[1]+"//"),p.type=n.method||n.type||p.method||p.type,p.dataTypes=x.trim(p.dataType||"*").toLowerCase().match(T)||[""],null==p.crossDomain&&(r=En.exec(p.url.toLowerCase()),p.crossDomain=!(!r||r[1]===mn[1]&&r[2]===mn[2]&&(r[3]||("http:"===r[1]?"80":"443"))===(mn[3]||("http:"===mn[1]?"80":"443")))),p.data&&p.processData&&"string"!=typeof p.data&&(p.data=x.param(p.data,p.traditional)),qn(An,p,n,C),2===b)return C;l=p.global,l&&0===x.active++&&x.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Nn.test(p.type),o=p.url,p.hasContent||(p.data&&(o=p.url+=(bn.test(o)?"&":"?")+p.data,delete p.data),p.cache===!1&&(p.url=wn.test(o)?o.replace(wn,"$1_="+vn++):o+(bn.test(o)?"&":"?")+"_="+vn++)),p.ifModified&&(x.lastModified[o]&&C.setRequestHeader("If-Modified-Since",x.lastModified[o]),x.etag[o]&&C.setRequestHeader("If-None-Match",x.etag[o])),(p.data&&p.hasContent&&p.contentType!==!1||n.contentType)&&C.setRequestHeader("Content-Type",p.contentType),C.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+Dn+"; q=0.01":""):p.accepts["*"]);for(i in p.headers)C.setRequestHeader(i,p.headers[i]);if(p.beforeSend&&(p.beforeSend.call(f,C,p)===!1||2===b))return C.abort();w="abort";for(i in{success:1,error:1,complete:1})C[i](p[i]);if(u=qn(jn,p,n,C)){C.readyState=1,l&&d.trigger("ajaxSend",[C,p]),p.async&&p.timeout>0&&(s=setTimeout(function(){C.abort("timeout")},p.timeout));try{b=1,u.send(y,k)}catch(N){if(!(2>b))throw N;k(-1,N)}}else k(-1,"No Transport");function k(e,n,r,i){var c,y,v,w,T,N=n;2!==b&&(b=2,s&&clearTimeout(s),u=t,a=i||"",C.readyState=e>0?4:0,c=e>=200&&300>e||304===e,r&&(w=Mn(p,C,r)),w=On(p,w,C,c),c?(p.ifModified&&(T=C.getResponseHeader("Last-Modified"),T&&(x.lastModified[o]=T),T=C.getResponseHeader("etag"),T&&(x.etag[o]=T)),204===e||"HEAD"===p.type?N="nocontent":304===e?N="notmodified":(N=w.state,y=w.data,v=w.error,c=!v)):(v=N,(e||!N)&&(N="error",0>e&&(e=0))),C.status=e,C.statusText=(n||N)+"",c?h.resolveWith(f,[y,N,C]):h.rejectWith(f,[C,N,v]),C.statusCode(m),m=t,l&&d.trigger(c?"ajaxSuccess":"ajaxError",[C,p,c?y:v]),g.fireWith(f,[C,N]),l&&(d.trigger("ajaxComplete",[C,p]),--x.active||x.event.trigger("ajaxStop")))}return C},getJSON:function(e,t,n){return x.get(e,t,n,"json")},getScript:function(e,n){return x.get(e,t,n,"script")}}),x.each(["get","post"],function(e,n){x[n]=function(e,r,i,o){return x.isFunction(r)&&(o=o||i,i=r,r=t),x.ajax({url:e,type:n,dataType:o,data:r,success:i})}});function Mn(e,n,r){var i,o,a,s,l=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),o===t&&(o=e.mimeType||n.getResponseHeader("Content-Type"));if(o)for(s in l)if(l[s]&&l[s].test(o)){u.unshift(s);break}if(u[0]in r)a=u[0];else{for(s in r){if(!u[0]||e.converters[s+" "+u[0]]){a=s;break}i||(i=s)}a=a||i}return a?(a!==u[0]&&u.unshift(a),r[a]):t}function On(e,t,n,r){var i,o,a,s,l,u={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)u[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!l&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),l=o,o=c.shift())if("*"===o)o=l;else if("*"!==l&&l!==o){if(a=u[l+" "+o]||u["* "+o],!a)for(i in u)if(s=i.split(" "),s[1]===o&&(a=u[l+" "+s[0]]||u["* "+s[0]])){a===!0?a=u[i]:u[i]!==!0&&(o=s[0],c.unshift(s[1]));break}if(a!==!0)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(p){return{state:"parsererror",error:a?p:"No conversion from "+l+" to "+o}}}return{state:"success",data:t}}x.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return x.globalEval(e),e}}}),x.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),x.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=a.head||x("head")[0]||a.documentElement;return{send:function(t,i){n=a.createElement("script"),n.async=!0,e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,t){(t||!n.readyState||/loaded|complete/.test(n.readyState))&&(n.onload=n.onreadystatechange=null,n.parentNode&&n.parentNode.removeChild(n),n=null,t||i(200,"success"))},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(t,!0)}}}});var Fn=[],Bn=/(=)\?(?=&|$)|\?\?/;x.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Fn.pop()||x.expando+"_"+vn++;return this[e]=!0,e}}),x.ajaxPrefilter("json jsonp",function(n,r,i){var o,a,s,l=n.jsonp!==!1&&(Bn.test(n.url)?"url":"string"==typeof n.data&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Bn.test(n.data)&&"data");return l||"jsonp"===n.dataTypes[0]?(o=n.jsonpCallback=x.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,l?n[l]=n[l].replace(Bn,"$1"+o):n.jsonp!==!1&&(n.url+=(bn.test(n.url)?"&":"?")+n.jsonp+"="+o),n.converters["script json"]=function(){return s||x.error(o+" was not called"),s[0]},n.dataTypes[0]="json",a=e[o],e[o]=function(){s=arguments},i.always(function(){e[o]=a,n[o]&&(n.jsonpCallback=r.jsonpCallback,Fn.push(o)),s&&x.isFunction(a)&&a(s[0]),s=a=t}),"script"):t});var Pn,Rn,Wn=0,$n=e.ActiveXObject&&function(){var e;for(e in Pn)Pn[e](t,!0)};function In(){try{return new e.XMLHttpRequest}catch(t){}}function zn(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}x.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&In()||zn()}:In,Rn=x.ajaxSettings.xhr(),x.support.cors=!!Rn&&"withCredentials"in Rn,Rn=x.support.ajax=!!Rn,Rn&&x.ajaxTransport(function(n){if(!n.crossDomain||x.support.cors){var r;return{send:function(i,o){var a,s,l=n.xhr();if(n.username?l.open(n.type,n.url,n.async,n.username,n.password):l.open(n.type,n.url,n.async),n.xhrFields)for(s in n.xhrFields)l[s]=n.xhrFields[s];n.mimeType&&l.overrideMimeType&&l.overrideMimeType(n.mimeType),n.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");try{for(s in i)l.setRequestHeader(s,i[s])}catch(u){}l.send(n.hasContent&&n.data||null),r=function(e,i){var s,u,c,p;try{if(r&&(i||4===l.readyState))if(r=t,a&&(l.onreadystatechange=x.noop,$n&&delete Pn[a]),i)4!==l.readyState&&l.abort();else{p={},s=l.status,u=l.getAllResponseHeaders(),"string"==typeof l.responseText&&(p.text=l.responseText);try{c=l.statusText}catch(f){c=""}s||!n.isLocal||n.crossDomain?1223===s&&(s=204):s=p.text?200:404}}catch(d){i||o(-1,d)}p&&o(s,c,p,u)},n.async?4===l.readyState?setTimeout(r):(a=++Wn,$n&&(Pn||(Pn={},x(e).unload($n)),Pn[a]=r),l.onreadystatechange=r):r()},abort:function(){r&&r(t,!0)}}}});var Xn,Un,Vn=/^(?:toggle|show|hide)$/,Yn=RegExp("^(?:([+-])=|)("+w+")([a-z%]*)$","i"),Jn=/queueHooks$/,Gn=[nr],Qn={"*":[function(e,t){var n=this.createTween(e,t),r=n.cur(),i=Yn.exec(t),o=i&&i[3]||(x.cssNumber[e]?"":"px"),a=(x.cssNumber[e]||"px"!==o&&+r)&&Yn.exec(x.css(n.elem,e)),s=1,l=20;if(a&&a[3]!==o){o=o||a[3],i=i||[],a=+r||1;do s=s||".5",a/=s,x.style(n.elem,e,a+o);while(s!==(s=n.cur()/r)&&1!==s&&--l)}return i&&(a=n.start=+a||+r||0,n.unit=o,n.end=i[1]?a+(i[1]+1)*i[2]:+i[2]),n}]};function Kn(){return setTimeout(function(){Xn=t}),Xn=x.now()}function Zn(e,t,n){var r,i=(Qn[t]||[]).concat(Qn["*"]),o=0,a=i.length;for(;a>o;o++)if(r=i[o].call(n,t,e))return r}function er(e,t,n){var r,i,o=0,a=Gn.length,s=x.Deferred().always(function(){delete l.elem}),l=function(){if(i)return!1;var t=Xn||Kn(),n=Math.max(0,u.startTime+u.duration-t),r=n/u.duration||0,o=1-r,a=0,l=u.tweens.length;for(;l>a;a++)u.tweens[a].run(o);return s.notifyWith(e,[u,o,n]),1>o&&l?n:(s.resolveWith(e,[u]),!1)},u=s.promise({elem:e,props:x.extend({},t),opts:x.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:Xn||Kn(),duration:n.duration,tweens:[],createTween:function(t,n){var r=x.Tween(e,u.opts,t,n,u.opts.specialEasing[t]||u.opts.easing);return u.tweens.push(r),r},stop:function(t){var n=0,r=t?u.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)u.tweens[n].run(1);return t?s.resolveWith(e,[u,t]):s.rejectWith(e,[u,t]),this}}),c=u.props;for(tr(c,u.opts.specialEasing);a>o;o++)if(r=Gn[o].call(u,e,c,u.opts))return r;return x.map(c,Zn,u),x.isFunction(u.opts.start)&&u.opts.start.call(e,u),x.fx.timer(x.extend(l,{elem:e,anim:u,queue:u.opts.queue})),u.progress(u.opts.progress).done(u.opts.done,u.opts.complete).fail(u.opts.fail).always(u.opts.always)}function tr(e,t){var n,r,i,o,a;for(n in e)if(r=x.camelCase(n),i=t[r],o=e[n],x.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),a=x.cssHooks[r],a&&"expand"in a){o=a.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}x.Animation=x.extend(er,{tweener:function(e,t){x.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;i>r;r++)n=e[r],Qn[n]=Qn[n]||[],Qn[n].unshift(t)},prefilter:function(e,t){t?Gn.unshift(e):Gn.push(e)}});function nr(e,t,n){var r,i,o,a,s,l,u=this,c={},p=e.style,f=e.nodeType&&nn(e),d=x._data(e,"fxshow");n.queue||(s=x._queueHooks(e,"fx"),null==s.unqueued&&(s.unqueued=0,l=s.empty.fire,s.empty.fire=function(){s.unqueued||l()}),s.unqueued++,u.always(function(){u.always(function(){s.unqueued--,x.queue(e,"fx").length||s.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[p.overflow,p.overflowX,p.overflowY],"inline"===x.css(e,"display")&&"none"===x.css(e,"float")&&(x.support.inlineBlockNeedsLayout&&"inline"!==ln(e.nodeName)?p.zoom=1:p.display="inline-block")),n.overflow&&(p.overflow="hidden",x.support.shrinkWrapBlocks||u.always(function(){p.overflow=n.overflow[0],p.overflowX=n.overflow[1],p.overflowY=n.overflow[2]}));for(r in t)if(i=t[r],Vn.exec(i)){if(delete t[r],o=o||"toggle"===i,i===(f?"hide":"show"))continue;c[r]=d&&d[r]||x.style(e,r)}if(!x.isEmptyObject(c)){d?"hidden"in d&&(f=d.hidden):d=x._data(e,"fxshow",{}),o&&(d.hidden=!f),f?x(e).show():u.done(function(){x(e).hide()}),u.done(function(){var t;x._removeData(e,"fxshow");for(t in c)x.style(e,t,c[t])});for(r in c)a=Zn(f?d[r]:0,r,u),r in d||(d[r]=a.start,f&&(a.end=a.start,a.start="width"===r||"height"===r?1:0))}}function rr(e,t,n,r,i){return new rr.prototype.init(e,t,n,r,i)}x.Tween=rr,rr.prototype={constructor:rr,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(x.cssNumber[n]?"":"px")},cur:function(){var e=rr.propHooks[this.prop];return e&&e.get?e.get(this):rr.propHooks._default.get(this)},run:function(e){var t,n=rr.propHooks[this.prop];return this.pos=t=this.options.duration?x.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):rr.propHooks._default.set(this),this}},rr.prototype.init.prototype=rr.prototype,rr.propHooks={_default:{get:function(e){var t;return null==e.elem[e.prop]||e.elem.style&&null!=e.elem.style[e.prop]?(t=x.css(e.elem,e.prop,""),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){x.fx.step[e.prop]?x.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[x.cssProps[e.prop]]||x.cssHooks[e.prop])?x.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},rr.propHooks.scrollTop=rr.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},x.each(["toggle","show","hide"],function(e,t){var n=x.fn[t];x.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(ir(t,!0),e,r,i)}}),x.fn.extend({fadeTo:function(e,t,n,r){return this.filter(nn).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=x.isEmptyObject(e),o=x.speed(t,n,r),a=function(){var t=er(this,x.extend({},e),o);(i||x._data(this,"finish"))&&t.stop(!0)};return a.finish=a,i||o.queue===!1?this.each(a):this.queue(o.queue,a)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return"string"!=typeof e&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=null!=e&&e+"queueHooks",o=x.timers,a=x._data(this);if(n)a[n]&&a[n].stop&&i(a[n]);else for(n in a)a[n]&&a[n].stop&&Jn.test(n)&&i(a[n]);for(n=o.length;n--;)o[n].elem!==this||null!=e&&o[n].queue!==e||(o[n].anim.stop(r),t=!1,o.splice(n,1));(t||!r)&&x.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=x._data(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=x.timers,a=r?r.length:0;for(n.finish=!0,x.queue(this,e,[]),i&&i.stop&&i.stop.call(this,!0),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;a>t;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}});function ir(e,t){var n,r={height:e},i=0;for(t=t?1:0;4>i;i+=2-t)n=Zt[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}x.each({slideDown:ir("show"),slideUp:ir("hide"),slideToggle:ir("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){x.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),x.speed=function(e,t,n){var r=e&&"object"==typeof e?x.extend({},e):{complete:n||!n&&t||x.isFunction(e)&&e,duration:e,easing:n&&t||t&&!x.isFunction(t)&&t};return r.duration=x.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in x.fx.speeds?x.fx.speeds[r.duration]:x.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){x.isFunction(r.old)&&r.old.call(this),r.queue&&x.dequeue(this,r.queue)},r},x.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},x.timers=[],x.fx=rr.prototype.init,x.fx.tick=function(){var e,n=x.timers,r=0;for(Xn=x.now();n.length>r;r++)e=n[r],e()||n[r]!==e||n.splice(r--,1);n.length||x.fx.stop(),Xn=t},x.fx.timer=function(e){e()&&x.timers.push(e)&&x.fx.start()},x.fx.interval=13,x.fx.start=function(){Un||(Un=setInterval(x.fx.tick,x.fx.interval))},x.fx.stop=function(){clearInterval(Un),Un=null},x.fx.speeds={slow:600,fast:200,_default:400},x.fx.step={},x.expr&&x.expr.filters&&(x.expr.filters.animated=function(e){return x.grep(x.timers,function(t){return e===t.elem}).length}),x.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){x.offset.setOffset(this,e,t)});var n,r,o={top:0,left:0},a=this[0],s=a&&a.ownerDocument;if(s)return n=s.documentElement,x.contains(n,a)?(typeof a.getBoundingClientRect!==i&&(o=a.getBoundingClientRect()),r=or(s),{top:o.top+(r.pageYOffset||n.scrollTop)-(n.clientTop||0),left:o.left+(r.pageXOffset||n.scrollLeft)-(n.clientLeft||0)}):o},x.offset={setOffset:function(e,t,n){var r=x.css(e,"position");"static"===r&&(e.style.position="relative");var i=x(e),o=i.offset(),a=x.css(e,"top"),s=x.css(e,"left"),l=("absolute"===r||"fixed"===r)&&x.inArray("auto",[a,s])>-1,u={},c={},p,f;l?(c=i.position(),p=c.top,f=c.left):(p=parseFloat(a)||0,f=parseFloat(s)||0),x.isFunction(t)&&(t=t.call(e,n,o)),null!=t.top&&(u.top=t.top-o.top+p),null!=t.left&&(u.left=t.left-o.left+f),"using"in t?t.using.call(e,u):i.css(u)}},x.fn.extend({position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===x.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),x.nodeName(e[0],"html")||(n=e.offset()),n.top+=x.css(e[0],"borderTopWidth",!0),n.left+=x.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-x.css(r,"marginTop",!0),left:t.left-n.left-x.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||s;while(e&&!x.nodeName(e,"html")&&"static"===x.css(e,"position"))e=e.offsetParent;return e||s})}}),x.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);x.fn[e]=function(i){return x.access(this,function(e,i,o){var a=or(e);return o===t?a?n in a?a[n]:a.document.documentElement[i]:e[i]:(a?a.scrollTo(r?x(a).scrollLeft():o,r?o:x(a).scrollTop()):e[i]=o,t)},e,i,arguments.length,null)}});function or(e){return x.isWindow(e)?e:9===e.nodeType?e.defaultView||e.parentWindow:!1}x.each({Height:"height",Width:"width"},function(e,n){x.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){x.fn[i]=function(i,o){var a=arguments.length&&(r||"boolean"!=typeof i),s=r||(i===!0||o===!0?"margin":"border");return x.access(this,function(n,r,i){var o;return x.isWindow(n)?n.document.documentElement["client"+e]:9===n.nodeType?(o=n.documentElement,Math.max(n.body["scroll"+e],o["scroll"+e],n.body["offset"+e],o["offset"+e],o["client"+e])):i===t?x.css(n,r,s):x.style(n,r,i,s)},n,a?i:t,a,null)}})}),x.fn.size=function(){return this.length},x.fn.andSelf=x.fn.addBack,"object"==typeof module&&module&&"object"==typeof module.exports?module.exports=x:(e.jQuery=e.$=x,"function"==typeof define&&define.amd&&define("jquery",[],function(){return x}))})(window); diff --git a/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/sep12.png b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/sep12.png Binary files differnew file mode 100644 index 00000000000..bb10431c778 --- /dev/null +++ b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/sep12.png diff --git a/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/sonar.css b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/sonar.css new file mode 100644 index 00000000000..40e929cf077 --- /dev/null +++ b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/sonar.css @@ -0,0 +1,396 @@ +html { + color: #111; + background: #FFF; +} +body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;} +body { + font: 13px/1.231 arial, helvetica, clean, sans-serif; +} +#content { + padding: 5px; +} +#reportHeader { + background-color: #262626; + height: 40px; + color: #FFF; + padding: 5px 10px 0 10px; + vertical-align: top; + text-align: center; +} +#logo { + float: right; +} +#reportHeader .title { + font-size: 18px; +} +#reportHeader .subtitle { + font-size: 11px; +} +#rules { + margin-bottom: 25px; +} +#rules a, #rules a:visited { + color: #111; +} +#rules .ruleSubtitle, #rules .ruleSubtitle a, #rules .ruleSubtitle a:visited { + font-size: 93%; + color: #777; +} +.globalIssues { + width: 100%; + border: 0; + margin-left: 37px; +} +hr { + border: none; + border-top: 2px dashed #DDD; + color: #FFF; + height: 10px; +} +div.banner { + height: 26px; + line-height: 26px; + background-color: #EEEEEE; + border: 1px solid #DDDDDD; + color: #444; + font-size: 85%; + margin-bottom: 10px; + padding: 0 5px; +} +table.data > thead > tr > th { + font-size: 93%; + padding: 4px 7px 4px 3px; + font-weight: normal; +} +table.data > tfoot > tr > td { + font-size: 93%; + color: #777; + padding: 4px 0 4px 10px; +} + +table.data > tbody > tr > td { + padding: 4px 7px 4px 3px; + vertical-align: text-top; +} + +table.data td.small, table.data th.small { + padding: 0; + white-space: nowrap; +} + +table.data th img, table.data td img { + vertical-align: text-bottom; +} + +.data thead tr.total { + background-color: #ECECEC; + font-weight: normal; + border: 1px solid #DDD; +} + +.data thead tr.total th { + font-weight: normal; +} + +table.data > thead { + border-bottom: 1px solid #ddd; +} + +table.data > tbody { + border-bottom: 1px solid #ddd; + border-right: 1px solid #ddd; + border-left: 1px solid #ddd; +} + +table.data, table.spaced, .gwt-SourcePanel .sources { + width: 100%; +} + +table.data>thead>tr>th { + font-size: 93%; + padding: 4px 7px 4px 3px; +} + +table.data>tfoot>tr>td { + font-size: 93%; + color: #777; + padding: 4px 0 4px 10px; +} + +table.data>tbody>tr>td { + padding: 4px 7px 4px 3px; + vertical-align: text-top; +} + +.data thead tr.total { + background-color: #ECECEC; +} + +.data thead tr.total th { + font-weight: bold; +} + +.hoverable:hover { + background-color: #f7f7f7; +} + +div, ul, li, h2, pre, form, input, td { + margin: 0; + padding: 0; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +img { + border: 0; +} + +select, input, textarea { + font: 99% arial, helvetica, clean, sans-serif; +} + +pre, code { + font-family: monospace; + line-height: 100%; +} + +code { + font-size: 93%; +} + +em { + font-weight: bold; +} + +h1 { + color: #444; + font-size: 16px; +} + +h2 { + color: #2B547D; + font-size: 16px; + font-weight: normal; +} + +h3 { + font-size: 100%; + font-weight: bold; +} + +h4 { + font-size: 85%; + color: #777; +} +td.sep { + width: 10px; +} +.sources { + width: 100%; + border-top: 1px solid #DDD; + border-bottom: 1px solid #DDD; + margin: 0 0 25px 0; +} + +.sources td.lid { + background-color: #ECECEC; + border-right: 1px solid #DDD; + border-left: 1px solid #DDD; + text-align: right; + padding: 2px .5em 0 .5em; + vertical-align: top; + font-size: 85%; + color: #444; + min-width: 25px; +} +div.issue { + background-color: #FFF; + border: 1px solid #DDD; + margin: 7px; +} + +div.vtitle { + background-color: #E4ECF3; + line-height: 2.2em; + height: 2.2em; + margin: 0; + padding: 0 0 0 10px; + text-shadow: 0 1px 0 #FFF; + color: #777; + vertical-align: middle; +} + +.rulename { + color: #444; + font-weight: bold; +} + +span.issue_date { + color: #777; + font-size: 90%; +} + +.sources td.line { + width: 100%; + border-right: 1px solid #DDD; +} + +.sources td.line pre { + font-size: 12px; + font-family: monospace; + margin-left: 1em; +} + +.sources td.ko { + background-color: #FF9090; +} + +.sources td.new_section { + border-top: 1px solid #DDD; + border-bottom: 1px solid #DDD; + height: 40px; +} + +td.issues { + background-color: #FFF; + border-right: 1px solid #DDD; + margin: 0; +} + +.sources pre { + font-family: Monospace; + margin: 0; + padding: 0 5px; + color: #333; + margin: 0; +} + +.file_title { + line-height: 1.5em; + height: 1.5em; + font-size: 1.2em; + margin: 10px 0 5px 0; + vertical-align: middle; +} + +.file_title img { + vertical-align: middle; +} + +.discussionComment.first { + border-top: none; +} + +div.discussionComment { + background-color: #F4F4F4; + border-top: 1px solid #DDD; + line-height: 1.5em; + margin: 0; + padding: 5px 10px; +} + +.rule_desc pre { + margin: 10px 0; + font-family: "Courier New", Courier, monospace;; + border: 1px dashed #aaa; + font-size: 93%; +} +span.better { + color: green; +} +span.worst { + color: red; +} +tr.blockSep { + display: none; +} +tr.blockSep.visible { + height: 20px; + line-height: 20px; + margin: 1px 0; + display: table-row !important; + display: block; /* hack for IE */ + border-top: 1px dashed #DDD; + border-bottom: 1px dashed #DDD; +} + +.big { + font-size:24px; + font-weight: bold; +} + +.masked { + display: none; +} + +.all-masked { + display: none; +} + +/** + Icons + */ + +@font-face { + font-family: 'sonar'; + src: url('sonar.eot'); + src: url('sonar.eot?#iefix') format('embedded-opentype'), url('sonar.ttf') format('truetype'), url('sonar.woff') format('woff'), url('sonar.svg#sonar') format('svg'); + font-weight: normal; + font-style: normal; +} +[class^="icon-"], +[class*=" icon-"] { + font-family: 'sonar'; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + vertical-align: middle; + /* Better Font Rendering =========== */ + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +/* + * Severity + */ +[class^="icon-severity-"], +[class*=" icon-severity"] { + position: relative; + top: -1px; +} +.icon-severity-blocker:before, +.icon-severity-4:before { + content: "\f000"; + color: #d4333f; + font-size: 14px; +} +.icon-severity-critical:before, +.icon-severity-3:before { + content: "\f001"; + color: #d4333f; + font-size: 14px; +} +.icon-severity-major:before, +.icon-severity-2:before { + content: "\f002"; + color: #d4333f; + font-size: 14px; +} +.icon-severity-minor:before, +.icon-severity-1:before { + content: "\f003"; + color: #85bb43; + font-size: 14px; +} +.icon-severity-info:before, +.icon-severity-0:before { + content: "\f004"; + color: #85bb43; + font-size: 14px; +} diff --git a/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/sonar.eot b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/sonar.eot Binary files differnew file mode 100755 index 00000000000..189f7b56818 --- /dev/null +++ b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/sonar.eot diff --git a/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/sonar.svg b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/sonar.svg new file mode 100755 index 00000000000..36965167290 --- /dev/null +++ b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/sonar.svg @@ -0,0 +1,33 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" > +<svg xmlns="http://www.w3.org/2000/svg"> +<metadata>Generated by IcoMoon</metadata> +<defs> +<font id="sonar" horiz-adv-x="1024"> +<font-face units-per-em="1024" ascent="960" descent="-64" /> +<missing-glyph horiz-adv-x="1024" /> +<glyph unicode=" " d="" horiz-adv-x="512" /> +<glyph unicode="" d="M438.857 865.524q119.429 0 220.286-58.857t159.714-159.714 58.857-220.286-58.857-220.286-159.714-159.714-220.286-58.857-220.286 58.857-159.714 159.714-58.857 220.286 58.857 220.286 159.714 159.714 220.286 58.857zM512 152.953v108.571q0 8-5.143 13.429t-12.571 5.429h-109.714q-7.429 0-13.143-5.714t-5.714-13.143v-108.571q0-7.429 5.714-13.143t13.143-5.714h109.714q7.429 0 12.571 5.429t5.143 13.429zM510.857 349.524l10.286 354.857q0 6.857-5.714 10.286-5.714 4.571-13.714 4.571h-125.714q-8 0-13.714-4.571-5.714-3.429-5.714-10.286l9.714-354.857q0-5.714 5.714-10t13.714-4.286h105.714q8 0 13.429 4.286t6 10z" /> +<glyph unicode="" d="M733.714 427.238q0 15.429-10.286 25.714l-258.857 258.857q-10.286 10.286-25.714 10.286t-25.714-10.286l-258.857-258.857q-10.286-10.286-10.286-25.714t10.286-25.714l52-52q10.286-10.286 25.714-10.286t25.714 10.286l108 108v-286.857q0-14.857 10.857-25.714t25.714-10.857h73.143q14.857 0 25.714 10.857t10.857 25.714v286.857l108-108q10.857-10.857 25.714-10.857t25.714 10.857l52 52q10.286 10.286 10.286 25.714zM877.714 426.667q0-119.429-58.857-220.286t-159.714-159.714-220.286-58.857-220.286 58.857-159.714 159.714-58.857 220.286 58.857 220.286 159.714 159.714 220.286 58.857 220.286-58.857 159.714-159.714 58.857-220.286z" /> +<glyph unicode="" d="M665.714 287.81l58.286 58.286q10.857 10.857 10.857 25.714t-10.857 25.714l-259.429 259.429q-10.857 10.857-25.714 10.857t-25.714-10.857l-259.429-259.429q-10.857-10.857-10.857-25.714t10.857-25.714l58.286-58.286q10.857-10.857 25.714-10.857t25.714 10.857l175.429 175.429 175.429-175.429q10.857-10.857 25.714-10.857t25.714 10.857zM877.714 426.667q0-119.429-58.857-220.286t-159.714-159.714-220.286-58.857-220.286 58.857-159.714 159.714-58.857 220.286 58.857 220.286 159.714 159.714 220.286 58.857 220.286-58.857 159.714-159.714 58.857-220.286z" /> +<glyph unicode="" d="M464.571 196.381l259.429 259.429q10.857 10.857 10.857 25.714t-10.857 25.714l-58.286 58.286q-10.857 10.857-25.714 10.857t-25.714-10.857l-175.429-175.429-175.429 175.429q-10.857 10.857-25.714 10.857t-25.714-10.857l-58.286-58.286q-10.857-10.857-10.857-25.714t10.857-25.714l259.429-259.429q10.857-10.857 25.714-10.857t25.714 10.857zM877.714 426.667q0-119.429-58.857-220.286t-159.714-159.714-220.286-58.857-220.286 58.857-159.714 159.714-58.857 220.286 58.857 220.286 159.714 159.714 220.286 58.857 220.286-58.857 159.714-159.714 58.857-220.286z" /> +<glyph unicode="" d="M733.714 426.096q0 15.429-10.286 25.714l-52 52q-10.286 10.286-25.714 10.286t-25.714-10.286l-108-108v286.857q0 14.857-10.857 25.714t-25.714 10.857h-73.143q-14.857 0-25.714-10.857t-10.857-25.714v-286.857l-108 108q-10.857 10.857-25.714 10.857t-25.714-10.857l-52-52q-10.286-10.286-10.286-25.714t10.286-25.714l258.857-258.857q10.286-10.286 25.714-10.286t25.714 10.286l258.857 258.857q10.286 10.286 10.286 25.714zM877.714 426.667q0-119.429-58.857-220.286t-159.714-159.714-220.286-58.857-220.286 58.857-159.714 159.714-58.857 220.286 58.857 220.286 159.714 159.714 220.286 58.857 220.286-58.857 159.714-159.714 58.857-220.286z" /> +<glyph unicode="" d="M438.857 737.524q-84.571 0-156-41.714t-113.143-113.143-41.714-156 41.714-156 113.143-113.143 156-41.714 156 41.714 113.143 113.143 41.714 156-41.714 156-113.143 113.143-156 41.714zM877.714 426.667q0-119.429-58.857-220.286t-159.714-159.714-220.286-58.857-220.286 58.857-159.714 159.714-58.857 220.286 58.857 220.286 159.714 159.714 220.286 58.857 220.286-58.857 159.714-159.714 58.857-220.286z" /> +<glyph unicode="" d="M585.143 426.667q0-60.571-42.857-103.429t-103.429-42.857-103.429 42.857-42.857 103.429 42.857 103.429 103.429 42.857 103.429-42.857 42.857-103.429zM438.857 737.524q-84.571 0-156-41.714t-113.143-113.143-41.714-156 41.714-156 113.143-113.143 156-41.714 156 41.714 113.143 113.143 41.714 156-41.714 156-113.143 113.143-156 41.714zM877.714 426.667q0-119.429-58.857-220.286t-159.714-159.714-220.286-58.857-220.286 58.857-159.714 159.714-58.857 220.286 58.857 220.286 159.714 159.714 220.286 58.857 220.286-58.857 159.714-159.714 58.857-220.286z" /> +<glyph unicode="" d="M438.857 115.81v621.714q-84.571 0-156-41.714t-113.143-113.143-41.714-156 41.714-156 113.143-113.143 156-41.714zM877.714 426.667q0-119.429-58.857-220.286t-159.714-159.714-220.286-58.857-220.286 58.857-159.714 159.714-58.857 220.286 58.857 220.286 159.714 159.714 220.286 58.857 220.286-58.857 159.714-159.714 58.857-220.286z" /> +<glyph unicode="" d="M733.714 519.238q0 16-10.286 26.286l-52 51.429q-10.857 10.857-25.714 10.857t-25.714-10.857l-233.143-232.571-129.143 129.143q-10.857 10.857-25.714 10.857t-25.714-10.857l-52-51.429q-10.286-10.286-10.286-26.286 0-15.429 10.286-25.714l206.857-206.857q10.857-10.857 25.714-10.857 15.429 0 26.286 10.857l310.286 310.286q10.286 10.286 10.286 25.714zM877.714 426.667q0-119.429-58.857-220.286t-159.714-159.714-220.286-58.857-220.286 58.857-159.714 159.714-58.857 220.286 58.857 220.286 159.714 159.714 220.286 58.857 220.286-58.857 159.714-159.714 58.857-220.286z" /> +<glyph unicode="" d="M877.714 426.667q0-119.429-58.857-220.286t-159.714-159.714-220.286-58.857-220.286 58.857-159.714 159.714-58.857 220.286 58.857 220.286 159.714 159.714 220.286 58.857 220.286-58.857 159.714-159.714 58.857-220.286z" /> +<glyph unicode="" d="M585.143 426.667q0 60.571-42.857 103.429t-103.429 42.857-103.429-42.857-42.857-103.429 42.857-103.429 103.429-42.857 103.429 42.857 42.857 103.429zM877.714 488.953v-126.857q0-6.857-4.571-13.143t-11.429-7.429l-105.714-16q-10.857-30.857-22.286-52 20-28.571 61.143-78.857 5.714-6.857 5.714-14.286t-5.143-13.143q-15.429-21.143-56.571-61.714t-53.714-40.571q-6.857 0-14.857 5.143l-78.857 61.714q-25.143-13.143-52-21.714-9.143-77.714-16.571-106.286-4-16-20.571-16h-126.857q-8 0-14 4.857t-6.571 12.286l-16 105.143q-28 9.143-51.429 21.143l-80.571-61.143q-5.714-5.143-14.286-5.143-8 0-14.286 6.286-72 65.143-94.286 96-4 5.714-4 13.143 0 6.857 4.571 13.143 8.571 12 29.143 38t30.857 40.286q-15.429 28.571-23.429 56.571l-104.571 15.429q-7.429 1.143-12 7.143t-4.571 13.429v126.857q0 6.857 4.571 13.143t10.857 7.429l106.286 16q8 26.286 22.286 52.571-22.857 32.571-61.143 78.857-5.714 6.857-5.714 13.714 0 5.714 5.143 13.143 14.857 20.571 56.286 61.429t54 40.857q7.429 0 14.857-5.714l78.857-61.143q25.143 13.143 52 21.714 9.143 77.714 16.571 106.286 4 16 20.571 16h126.857q8 0 14-4.857t6.571-12.286l16-105.143q28-9.143 51.429-21.143l81.143 61.143q5.143 5.143 13.714 5.143 7.429 0 14.286-5.714 73.714-68 94.286-97.143 4-4.571 4-12.571 0-6.857-4.571-13.143-8.571-12-29.143-38t-30.857-40.286q14.857-28.571 23.429-56l104.571-16q7.429-1.143 12-7.143t4.571-13.429z" /> +<glyph unicode="" d="M1024 170.667v-73.143q0-14.857-10.857-25.714t-25.714-10.857h-950.857q-14.857 0-25.714 10.857t-10.857 25.714v73.143q0 14.857 10.857 25.714t25.714 10.857h950.857q14.857 0 25.714-10.857t10.857-25.714zM1024 390.096v-73.143q0-14.857-10.857-25.714t-25.714-10.857h-950.857q-14.857 0-25.714 10.857t-10.857 25.714v73.143q0 14.857 10.857 25.714t25.714 10.857h950.857q14.857 0 25.714-10.857t10.857-25.714zM1024 609.524v-73.143q0-14.857-10.857-25.714t-25.714-10.857h-950.857q-14.857 0-25.714 10.857t-10.857 25.714v73.143q0 14.857 10.857 25.714t25.714 10.857h950.857q14.857 0 25.714-10.857t10.857-25.714zM1024 828.953v-73.143q0-14.857-10.857-25.714t-25.714-10.857h-950.857q-14.857 0-25.714 10.857t-10.857 25.714v73.143q0 14.857 10.857 25.714t25.714 10.857h950.857q14.857 0 25.714-10.857t10.857-25.714z" /> +<glyph unicode="" d="M626.857 322.096l-83.429-83.429q-5.714-5.714-13.143-5.714t-13.143 5.714l-78.286 78.286-78.286-78.286q-5.714-5.714-13.143-5.714t-13.143 5.714l-83.429 83.429q-5.714 5.714-5.714 13.143t5.714 13.143l78.286 78.286-78.286 78.286q-5.714 5.714-5.714 13.143t5.714 13.143l83.429 83.429q5.714 5.714 13.143 5.714t13.143-5.714l78.286-78.286 78.286 78.286q5.714 5.714 13.143 5.714t13.143-5.714l83.429-83.429q5.714-5.714 5.714-13.143t-5.714-13.143l-78.286-78.286 78.286-78.286q5.714-5.714 5.714-13.143t-5.714-13.143zM749.714 426.667q0 84.571-41.714 156t-113.143 113.143-156 41.714-156-41.714-113.143-113.143-41.714-156 41.714-156 113.143-113.143 156-41.714 156 41.714 113.143 113.143 41.714 156zM877.714 426.667q0-119.429-58.857-220.286t-159.714-159.714-220.286-58.857-220.286 58.857-159.714 159.714-58.857 220.286 58.857 220.286 159.714 159.714 220.286 58.857 220.286-58.857 159.714-159.714 58.857-220.286z" /> +<glyph unicode="" d="M669.143 474.096l-241.143-241.143q-10.857-10.857-25.714-10.857t-25.714 10.857l-168 168q-10.857 10.857-10.857 25.714t10.857 25.714l58.286 58.286q10.857 10.857 25.714 10.857t25.714-10.857l84-84 157.143 157.143q10.857 10.857 25.714 10.857t25.714-10.857l58.286-58.286q10.857-10.857 10.857-25.714t-10.857-25.714zM749.714 426.667q0 84.571-41.714 156t-113.143 113.143-156 41.714-156-41.714-113.143-113.143-41.714-156 41.714-156 113.143-113.143 156-41.714 156 41.714 113.143 113.143 41.714 156zM877.714 426.667q0-119.429-58.857-220.286t-159.714-159.714-220.286-58.857-220.286 58.857-159.714 159.714-58.857 220.286 58.857 220.286 159.714 159.714 220.286 58.857 220.286-58.857 159.714-159.714 58.857-220.286z" /> +<glyph unicode="" d="M749.714 428.381q0 92-49.714 168.571l-430.857-430.286q78.286-50.857 169.714-50.857 63.429 0 120.857 24.857t99.143 66.571 66.286 99.714 24.571 121.429zM178.857 257.524l431.429 430.857q-77.143 52-171.429 52-84.571 0-156-41.714t-113.143-113.714-41.714-156.571q0-92.571 50.857-170.857zM877.714 428.381q0-89.714-34.857-171.429t-93.429-140.571-140-93.714-170.571-34.857-170.571 34.857-140 93.714-93.429 140.571-34.857 171.429 34.857 171.143 93.429 140.286 140 93.714 170.571 34.857 170.571-34.857 140-93.714 93.429-140.286 34.857-171.143z" /> +<glyph unicode="" d="M512 426.667q0 60.571-42.857 103.429t-103.429 42.857-103.429-42.857-42.857-103.429 42.857-103.429 103.429-42.857 103.429 42.857 42.857 103.429zM950.857 134.096q0 29.714-21.714 51.429t-51.429 21.714-51.429-21.714-21.714-51.429q0-30.286 21.429-51.714t51.714-21.429 51.714 21.429 21.429 51.714zM950.857 719.238q0 29.714-21.714 51.429t-51.429 21.714-51.429-21.714-21.714-51.429q0-30.286 21.429-51.714t51.714-21.429 51.714 21.429 21.429 51.714zM731.429 478.667v-105.714q0-5.714-4-11.143t-9.143-6l-88.571-13.714q-6.286-20-18.286-43.429 19.429-27.429 51.429-65.714 4-5.714 4-11.429 0-6.857-4-10.857-13.143-17.143-47.143-51.143t-44.857-34q-6.286 0-12 4l-65.714 51.429q-21.143-10.857-44-17.714-6.286-61.714-13.143-88.571-4-13.714-17.143-13.714h-106.286q-6.286 0-11.429 4.286t-5.714 10l-13.143 87.429q-19.429 5.714-42.857 17.714l-67.429-50.857q-4-4-11.429-4-6.286 0-12 4.571-82.286 76-82.286 91.429 0 5.143 4 10.857 5.714 8 23.429 30.286t26.857 34.857q-13.143 25.143-20 46.857l-86.857 13.714q-5.714 0.571-9.714 5.429t-4 11.143v105.714q0 5.714 4 11.143t9.143 6l88.571 13.714q6.286 20 18.286 43.429-19.429 27.429-51.429 65.714-4 6.286-4 11.429 0 6.857 4 11.429 12.571 17.143 46.857 50.857t45.143 33.714q6.286 0 12-4l65.714-51.429q19.429 10.286 44 18.286 6.286 61.714 13.143 88 4 13.714 17.143 13.714h106.286q6.286 0 11.429-4.286t5.714-10l13.143-87.429q19.429-5.714 42.857-17.714l67.429 50.857q4.571 4 11.429 4 6.286 0 12-4.571 82.286-76 82.286-91.429 0-5.143-4-10.857-6.857-9.143-24-30.857t-25.714-34.286q13.143-27.429 19.429-46.857l86.857-13.143q5.714-1.143 9.714-6t4-11.143zM1097.143 174.096v-80q0-9.143-85.143-17.714-6.857-15.429-17.143-29.714 29.143-64.571 29.143-78.857 0-2.286-2.286-4-69.714-40.571-70.857-40.571-4.571 0-26.286 26.857t-29.714 38.857q-11.429-1.143-17.143-1.143t-17.143 1.143q-8-12-29.714-38.857t-26.286-26.857q-1.143 0-70.857 40.571-2.286 1.714-2.286 4 0 14.286 29.143 78.857-10.286 14.286-17.143 29.714-85.143 8.571-85.143 17.714v80q0 9.143 85.143 17.714 7.429 16.571 17.143 29.714-29.143 64.571-29.143 78.857 0 2.286 2.286 4 2.286 1.143 20 11.429t33.714 19.429 17.143 9.143q4.571 0 26.286-26.571t29.714-38.571q11.429 1.143 17.143 1.143t17.143-1.143q29.143 40.571 52.571 64l3.429 1.143q2.286 0 70.857-40 2.286-1.714 2.286-4 0-14.286-29.143-78.857 9.714-13.143 17.143-29.714 85.143-8.571 85.143-17.714zM1097.143 759.238v-80q0-9.143-85.143-17.714-6.857-15.429-17.143-29.714 29.143-64.571 29.143-78.857 0-2.286-2.286-4-69.714-40.571-70.857-40.571-4.571 0-26.286 26.857t-29.714 38.857q-11.429-1.143-17.143-1.143t-17.143 1.143q-8-12-29.714-38.857t-26.286-26.857q-1.143 0-70.857 40.571-2.286 1.714-2.286 4 0 14.286 29.143 78.857-10.286 14.286-17.143 29.714-85.143 8.571-85.143 17.714v80q0 9.143 85.143 17.714 7.429 16.571 17.143 29.714-29.143 64.571-29.143 78.857 0 2.286 2.286 4 2.286 1.143 20 11.429t33.714 19.429 17.143 9.143q4.571 0 26.286-26.571t29.714-38.571q11.429 1.143 17.143 1.143t17.143-1.143q29.143 40.571 52.571 64l3.429 1.143q2.286 0 70.857-40 2.286-1.714 2.286-4 0-14.286-29.143-78.857 9.714-13.143 17.143-29.714 85.143-8.571 85.143-17.714z" horiz-adv-x="1097" /> +<glyph unicode="" d="M585.143 536.381q0-14.857-10.857-25.714l-256-256q-10.857-10.857-25.714-10.857t-25.714 10.857l-256 256q-10.857 10.857-10.857 25.714t10.857 25.714 25.714 10.857h512q14.857 0 25.714-10.857t10.857-25.714z" horiz-adv-x="585" /> +<glyph unicode="" d="M585.143 243.81q0-14.857-10.857-25.714t-25.714-10.857h-512q-14.857 0-25.714 10.857t-10.857 25.714 10.857 25.714l256 256q10.857 10.857 25.714 10.857t25.714-10.857l256-256q10.857-10.857 10.857-25.714z" horiz-adv-x="585" /> +<glyph unicode="" d="M365.714 682.667v-512q0-14.857-10.857-25.714t-25.714-10.857-25.714 10.857l-256 256q-10.857 10.857-10.857 25.714t10.857 25.714l256 256q10.857 10.857 25.714 10.857t25.714-10.857 10.857-25.714z" horiz-adv-x="366" /> +<glyph unicode="" d="M329.143 426.667q0-14.857-10.857-25.714l-256-256q-10.857-10.857-25.714-10.857t-25.714 10.857-10.857 25.714v512q0 14.857 10.857 25.714t25.714 10.857 25.714-10.857l256-256q10.857-10.857 10.857-25.714z" horiz-adv-x="366" /> +<glyph unicode="" d="M648 324.381q-21.143-69.143-78.857-111.429t-130.286-42.286-130.286 42.286-78.857 111.429q-4.571 14.286 2.286 27.714t21.714 18q14.286 4.571 27.714-2.286t18-21.714q14.286-45.714 52.857-74t86.571-28.286 86.571 28.286 52.857 74q4.571 14.857 18.286 21.714t28 2.286 21.143-18 2.286-27.714zM365.714 572.953q0-30.286-21.429-51.714t-51.714-21.429-51.714 21.429-21.429 51.714 21.429 51.714 51.714 21.429 51.714-21.429 21.429-51.714zM658.286 572.953q0-30.286-21.429-51.714t-51.714-21.429-51.714 21.429-21.429 51.714 21.429 51.714 51.714 21.429 51.714-21.429 21.429-51.714zM804.571 426.667q0 74.286-29.143 142t-78 116.571-116.571 78-142 29.143-142-29.143-116.571-78-78-116.571-29.143-142 29.143-142 78-116.571 116.571-78 142-29.143 142 29.143 116.571 78 78 116.571 29.143 142zM877.714 426.667q0-119.429-58.857-220.286t-159.714-159.714-220.286-58.857-220.286 58.857-159.714 159.714-58.857 220.286 58.857 220.286 159.714 159.714 220.286 58.857 220.286-58.857 159.714-159.714 58.857-220.286z" /> +<glyph unicode="" d="M648 236.381q4.571-14.286-2.286-27.714t-21.143-18-28 2.286-18.286 21.714q-14.286 45.714-52.857 74t-86.571 28.286-86.571-28.286-52.857-74q-4.571-14.857-18-21.714t-27.714-2.286q-14.857 4.571-21.714 18t-2.286 27.714q21.143 69.143 78.857 111.429t130.286 42.286 130.286-42.286 78.857-111.429zM365.714 572.953q0-30.286-21.429-51.714t-51.714-21.429-51.714 21.429-21.429 51.714 21.429 51.714 51.714 21.429 51.714-21.429 21.429-51.714zM658.286 572.953q0-30.286-21.429-51.714t-51.714-21.429-51.714 21.429-21.429 51.714 21.429 51.714 51.714 21.429 51.714-21.429 21.429-51.714zM804.571 426.667q0 74.286-29.143 142t-78 116.571-116.571 78-142 29.143-142-29.143-116.571-78-78-116.571-29.143-142 29.143-142 78-116.571 116.571-78 142-29.143 142 29.143 116.571 78 78 116.571 29.143 142zM877.714 426.667q0-119.429-58.857-220.286t-159.714-159.714-220.286-58.857-220.286 58.857-159.714 159.714-58.857 220.286 58.857 220.286 159.714 159.714 220.286 58.857 220.286-58.857 159.714-159.714 58.857-220.286z" /> +<glyph unicode="" d="M658.286 316.953q0-14.857-10.857-25.714t-25.714-10.857h-365.714q-14.857 0-25.714 10.857t-10.857 25.714 10.857 25.714 25.714 10.857h365.714q14.857 0 25.714-10.857t10.857-25.714zM365.714 572.953q0-30.286-21.429-51.714t-51.714-21.429-51.714 21.429-21.429 51.714 21.429 51.714 51.714 21.429 51.714-21.429 21.429-51.714zM658.286 572.953q0-30.286-21.429-51.714t-51.714-21.429-51.714 21.429-21.429 51.714 21.429 51.714 51.714 21.429 51.714-21.429 21.429-51.714zM804.571 426.667q0 74.286-29.143 142t-78 116.571-116.571 78-142 29.143-142-29.143-116.571-78-78-116.571-29.143-142 29.143-142 78-116.571 116.571-78 142-29.143 142 29.143 116.571 78 78 116.571 29.143 142zM877.714 426.667q0-119.429-58.857-220.286t-159.714-159.714-220.286-58.857-220.286 58.857-159.714 159.714-58.857 220.286 58.857 220.286 159.714 159.714 220.286 58.857 220.286-58.857 159.714-159.714 58.857-220.286z" /> +</font></defs></svg>
\ No newline at end of file diff --git a/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/sonar.ttf b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/sonar.ttf Binary files differnew file mode 100755 index 00000000000..5a876906e10 --- /dev/null +++ b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/sonar.ttf diff --git a/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/sonar.woff b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/sonar.woff Binary files differnew file mode 100755 index 00000000000..bf0cf103cd0 --- /dev/null +++ b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/sonar.woff diff --git a/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/sonarqube-24x100.png b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/sonarqube-24x100.png Binary files differnew file mode 100644 index 00000000000..b2ff23bf288 --- /dev/null +++ b/sonar-scanner-engine/src/main/resources/org/sonar/batch/scan/report/issuesreport_files/sonarqube-24x100.png diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/DefaultFileLinesContextTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/DefaultFileLinesContextTest.java new file mode 100644 index 00000000000..eb622b003fa --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/DefaultFileLinesContextTest.java @@ -0,0 +1,148 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Matchers; +import org.sonar.api.batch.SonarIndex; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.Metric; +import org.sonar.api.measures.PersistenceMode; +import org.sonar.api.resources.Directory; +import org.sonar.api.resources.Resource; +import org.sonar.api.resources.Scopes; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class DefaultFileLinesContextTest { + + private SonarIndex index; + private Resource resource; + private DefaultFileLinesContext fileLineMeasures; + + @Before + public void setUp() { + index = mock(SonarIndex.class); + resource = mock(Resource.class); + when(resource.getScope()).thenReturn(Scopes.FILE); + fileLineMeasures = new DefaultFileLinesContext(index, resource); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldNotAllowCreationForDirectory() { + new DefaultFileLinesContext(index, Directory.create("key")); + } + + @Test + public void shouldSave() { + fileLineMeasures.setIntValue("hits", 1, 2); + fileLineMeasures.setIntValue("hits", 3, 4); + fileLineMeasures.save(); + + assertThat(fileLineMeasures.toString()).isEqualTo("DefaultFileLinesContext{map={hits={1=2, 3=4}}}"); + + ArgumentCaptor<Measure> measureCaptor = ArgumentCaptor.forClass(Measure.class); + verify(index).addMeasure(Matchers.eq(resource), measureCaptor.capture()); + Measure measure = measureCaptor.getValue(); + assertThat(measure.getMetricKey(), is("hits")); + assertThat(measure.getPersistenceMode(), is(PersistenceMode.DATABASE)); + assertThat(measure.getData(), is("1=2;3=4")); + } + + @Test + public void shouldSaveSeveral() { + fileLineMeasures.setIntValue("hits", 1, 2); + fileLineMeasures.setIntValue("hits", 3, 4); + fileLineMeasures.setStringValue("author", 1, "simon"); + fileLineMeasures.setStringValue("author", 3, "evgeny"); + fileLineMeasures.save(); + fileLineMeasures.setIntValue("branches", 1, 2); + fileLineMeasures.setIntValue("branches", 3, 4); + fileLineMeasures.save(); + + verify(index, times(3)).addMeasure(Matchers.eq(resource), Matchers.any(Measure.class)); + } + + @Test(expected = UnsupportedOperationException.class) + public void shouldNotModifyAfterSave() { + fileLineMeasures.setIntValue("hits", 1, 2); + fileLineMeasures.save(); + fileLineMeasures.save(); + verify(index).addMeasure(Matchers.eq(resource), Matchers.any(Measure.class)); + fileLineMeasures.setIntValue("hits", 1, 2); + } + + @Test + public void shouldLoadIntValues() { + when(index.getMeasure(Matchers.any(Resource.class), Matchers.any(Metric.class))) + .thenReturn(new Measure("hits").setData("1=2;3=4")); + + assertThat(fileLineMeasures.getIntValue("hits", 1), is(2)); + assertThat(fileLineMeasures.getIntValue("hits", 3), is(4)); + assertThat("no measure on line", fileLineMeasures.getIntValue("hits", 5), nullValue()); + } + + @Test + public void shouldLoadStringValues() { + when(index.getMeasure(Matchers.any(Resource.class), Matchers.any(Metric.class))) + .thenReturn(new Measure("author").setData("1=simon;3=evgeny")); + + assertThat(fileLineMeasures.getStringValue("author", 1), is("simon")); + assertThat(fileLineMeasures.getStringValue("author", 3), is("evgeny")); + assertThat("no measure on line", fileLineMeasures.getStringValue("author", 5), nullValue()); + } + + @Test + public void shouldNotSaveAfterLoad() { + when(index.getMeasure(Matchers.any(Resource.class), Matchers.any(Metric.class))) + .thenReturn(new Measure("author").setData("1=simon;3=evgeny")); + + fileLineMeasures.getStringValue("author", 1); + fileLineMeasures.save(); + + verify(index, never()).addMeasure(Matchers.eq(resource), Matchers.any(Measure.class)); + } + + @Test(expected = UnsupportedOperationException.class) + public void shouldNotModifyAfterLoad() { + when(index.getMeasure(Matchers.any(Resource.class), Matchers.any(Metric.class))) + .thenReturn(new Measure("author").setData("1=simon;3=evgeny")); + + fileLineMeasures.getStringValue("author", 1); + fileLineMeasures.setStringValue("author", 1, "evgeny"); + } + + @Test + public void shouldNotFailIfNoMeasureInIndex() { + assertThat(fileLineMeasures.getIntValue("hits", 1), nullValue()); + assertThat(fileLineMeasures.getStringValue("author", 1), nullValue()); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/ProjectConfiguratorTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/ProjectConfiguratorTest.java new file mode 100644 index 00000000000..6a30188454c --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/ProjectConfiguratorTest.java @@ -0,0 +1,130 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch; + +import java.text.SimpleDateFormat; +import java.util.TimeZone; +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.CoreProperties; +import org.sonar.api.config.Settings; +import org.sonar.api.resources.Project; +import org.sonar.api.utils.System2; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ProjectConfiguratorTest { + + System2 system2; + + @Before + public void setUp() { + system2 = mock(System2.class); + } + + @Test + public void analysis_is_today_by_default() { + Long now = System.currentTimeMillis(); + when(system2.now()).thenReturn(now); + + Project project = new Project("key"); + new ProjectConfigurator(new Settings(), system2).configure(project); + assertThat(now - project.getAnalysisDate().getTime()).isLessThan(1000); + } + + @Test + public void analysis_date_could_be_explicitly_set() { + Settings settings = new Settings(); + settings.setProperty(CoreProperties.PROJECT_DATE_PROPERTY, "2005-01-30"); + Project project = new Project("key"); + new ProjectConfigurator(settings, system2).configure(project); + + assertThat(new SimpleDateFormat("ddMMyyyy").format(project.getAnalysisDate())).isEqualTo("30012005"); + } + + @Test + public void analysis_timestamp_could_be_explicitly_set() { + Settings settings = new Settings(); + settings.setProperty(CoreProperties.PROJECT_DATE_PROPERTY, "2005-01-30T08:45:10+0000"); + Project project = new Project("key"); + new ProjectConfigurator(settings, system2).configure(project); + + SimpleDateFormat dateFormat = new SimpleDateFormat("ddMMyyyy-mmss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); + assertThat(dateFormat.format(project.getAnalysisDate())).isEqualTo("30012005-4510"); + } + + @Test(expected = RuntimeException.class) + public void fail_if_analyis_date_is_not_valid() { + Settings configuration = new Settings(); + configuration.setProperty(CoreProperties.PROJECT_DATE_PROPERTY, "2005/30/01"); + Project project = new Project("key"); + new ProjectConfigurator(configuration, system2).configure(project); + } + + @Test + public void default_analysis_type_is_dynamic() { + Project project = new Project("key"); + new ProjectConfigurator(new Settings(), system2).configure(project); + assertThat(project.getAnalysisType()).isEqualTo(Project.AnalysisType.DYNAMIC); + } + + @Test + public void explicit_dynamic_analysis() { + Settings configuration = new Settings(); + configuration.setProperty(CoreProperties.DYNAMIC_ANALYSIS_PROPERTY, "true"); + Project project = new Project("key"); + new ProjectConfigurator(configuration, system2).configure(project); + assertThat(project.getAnalysisType()).isEqualTo(Project.AnalysisType.DYNAMIC); + } + + @Test + public void explicit_static_analysis() { + Settings configuration = new Settings(); + configuration.setProperty(CoreProperties.DYNAMIC_ANALYSIS_PROPERTY, "false"); + Project project = new Project("key"); + new ProjectConfigurator(configuration, system2).configure(project); + assertThat(project.getAnalysisType()).isEqualTo(Project.AnalysisType.STATIC); + } + + @Test + public void explicit_dynamic_analysis_reusing_reports() { + Settings configuration = new Settings(); + configuration.setProperty(CoreProperties.DYNAMIC_ANALYSIS_PROPERTY, "reuseReports"); + Project project = new Project("key"); + new ProjectConfigurator(configuration, system2).configure(project); + assertThat(project.getAnalysisType()).isEqualTo(Project.AnalysisType.REUSE_REPORTS); + } + + @Test + public void is_dynamic_analysis() { + assertThat(Project.AnalysisType.DYNAMIC.isDynamic(false)).isTrue(); + assertThat(Project.AnalysisType.DYNAMIC.isDynamic(true)).isTrue(); + + assertThat(Project.AnalysisType.STATIC.isDynamic(false)).isFalse(); + assertThat(Project.AnalysisType.STATIC.isDynamic(true)).isFalse(); + + assertThat(Project.AnalysisType.REUSE_REPORTS.isDynamic(false)).isFalse(); + assertThat(Project.AnalysisType.REUSE_REPORTS.isDynamic(true)).isTrue(); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/analysis/AnalysisTempFolderProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/analysis/AnalysisTempFolderProviderTest.java new file mode 100644 index 00000000000..058ea4fd34c --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/analysis/AnalysisTempFolderProviderTest.java @@ -0,0 +1,66 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.analysis; + +import org.sonar.api.batch.bootstrap.ProjectDefinition; + +import org.sonar.api.batch.bootstrap.ProjectReactor; +import org.junit.Before; +import org.sonar.batch.analysis.AnalysisTempFolderProvider; +import org.sonar.api.utils.TempFolder; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; + +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.mock; +import static org.assertj.core.api.Assertions.assertThat; + +public class AnalysisTempFolderProviderTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private AnalysisTempFolderProvider tempFolderProvider; + private ProjectReactor projectReactor; + + @Before + public void setUp() { + tempFolderProvider = new AnalysisTempFolderProvider(); + projectReactor = mock(ProjectReactor.class); + ProjectDefinition projectDefinition = mock(ProjectDefinition.class); + when(projectReactor.getRoot()).thenReturn(projectDefinition); + when(projectDefinition.getWorkDir()).thenReturn(temp.getRoot()); + } + + @Test + public void createTempFolder() throws IOException { + File defaultDir = new File(temp.getRoot(), AnalysisTempFolderProvider.TMP_NAME); + + TempFolder tempFolder = tempFolderProvider.provide(projectReactor); + tempFolder.newDir(); + tempFolder.newFile(); + assertThat(defaultDir).exists(); + assertThat(defaultDir.list()).hasSize(2); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/analysis/AnalysisWSLoaderProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/analysis/AnalysisWSLoaderProviderTest.java new file mode 100644 index 00000000000..66ab296a063 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/analysis/AnalysisWSLoaderProviderTest.java @@ -0,0 +1,62 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.analysis; + +import com.google.common.collect.ImmutableMap; +import org.assertj.core.util.Maps; +import org.junit.Test; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.batch.bootstrap.BatchWsClient; +import org.sonar.batch.cache.WSLoader; +import org.sonar.batch.cache.WSLoader.LoadStrategy; +import org.sonar.home.cache.PersistentCache; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class AnalysisWSLoaderProviderTest { + + PersistentCache cache = mock(PersistentCache.class); + BatchWsClient wsClient = mock(BatchWsClient.class); + AnalysisMode mode = mock(AnalysisMode.class); + + AnalysisWSLoaderProvider underTest = new AnalysisWSLoaderProvider(); + + @Test + public void testDefault() { + WSLoader loader = underTest.provide(mode, cache, wsClient, new AnalysisProperties(Maps.<String, String>newHashMap())); + assertThat(loader.getDefaultStrategy()).isEqualTo(LoadStrategy.SERVER_ONLY); + } + + @Test + public void no_cache_by_default_in_issues_mode() { + when(mode.isIssues()).thenReturn(true); + WSLoader loader = underTest.provide(mode, cache, wsClient, new AnalysisProperties(Maps.<String, String>newHashMap())); + assertThat(loader.getDefaultStrategy()).isEqualTo(LoadStrategy.SERVER_ONLY); + } + + @Test + public void enable_cache_in_issues_mode() { + when(mode.isIssues()).thenReturn(true); + WSLoader loader = underTest.provide(mode, cache, wsClient, new AnalysisProperties(ImmutableMap.of(AnalysisWSLoaderProvider.SONAR_USE_WS_CACHE, "true"))); + assertThat(loader.getDefaultStrategy()).isEqualTo(LoadStrategy.CACHE_ONLY); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/analysis/DefaultAnalysisModeTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/analysis/DefaultAnalysisModeTest.java new file mode 100644 index 00000000000..67734ed62fa --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/analysis/DefaultAnalysisModeTest.java @@ -0,0 +1,141 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.analysis; + +import org.junit.Rule; +import org.junit.rules.ExpectedException; +import org.sonar.batch.analysis.DefaultAnalysisMode; +import org.sonar.batch.analysis.AnalysisProperties; + +import javax.annotation.Nullable; + +import org.sonar.batch.bootstrap.GlobalProperties; +import org.junit.Test; +import org.sonar.api.CoreProperties; + +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DefaultAnalysisModeTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void regular_analysis_by_default() { + DefaultAnalysisMode mode = createMode(null, null); + assertThat(mode.isPreview()).isFalse(); + assertThat(mode.isPublish()).isTrue(); + } + + @Test(expected = IllegalStateException.class) + public void fail_if_inconsistent() { + createMode(null, CoreProperties.ANALYSIS_MODE_ISSUES); + } + + @Test + public void support_publish_mode() { + DefaultAnalysisMode mode = createMode(CoreProperties.ANALYSIS_MODE_PUBLISH); + + assertThat(mode.isPreview()).isFalse(); + assertThat(mode.isPublish()).isTrue(); + } + + @Test + public void incremental_mode_no_longer_valid() { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("This mode was removed in SonarQube 5.2"); + + createMode(CoreProperties.ANALYSIS_MODE_INCREMENTAL); + } + + @Test + public void invalidate_mode() { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("[preview, publish, issues]"); + + createMode("invalid"); + } + + @Test + public void preview_mode_fallback_issues() { + DefaultAnalysisMode mode = createMode(CoreProperties.ANALYSIS_MODE_PREVIEW); + + assertThat(mode.isIssues()).isTrue(); + assertThat(mode.isPreview()).isFalse(); + } + + @Test + public void scan_all() { + Map<String, String> props = new HashMap<>(); + props.put(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_ISSUES); + GlobalProperties globalProps = new GlobalProperties(props); + + AnalysisProperties analysisProps = new AnalysisProperties(new HashMap<String, String>()); + DefaultAnalysisMode mode = new DefaultAnalysisMode(globalProps, analysisProps); + assertThat(mode.scanAllFiles()).isFalse(); + + props.put("sonar.scanAllFiles", "true"); + analysisProps = new AnalysisProperties(props); + + mode = new DefaultAnalysisMode(globalProps, analysisProps); + assertThat(mode.scanAllFiles()).isTrue(); + + props.put(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_PUBLISH); + analysisProps = new AnalysisProperties(props); + + mode = new DefaultAnalysisMode(globalProps, analysisProps); + assertThat(mode.scanAllFiles()).isTrue(); + } + + @Test + public void default_publish_mode() { + DefaultAnalysisMode mode = createMode(null); + assertThat(mode.isPublish()).isTrue(); + assertThat(mode.scanAllFiles()).isTrue(); + } + + @Test + public void support_issues_mode() { + DefaultAnalysisMode mode = createMode(CoreProperties.ANALYSIS_MODE_ISSUES); + + assertThat(mode.isIssues()).isTrue(); + assertThat(mode.scanAllFiles()).isFalse(); + } + + private static DefaultAnalysisMode createMode(@Nullable String mode) { + return createMode(mode, mode); + } + + private static DefaultAnalysisMode createMode(@Nullable String bootstrapMode, @Nullable String analysisMode) { + Map<String, String> bootstrapMap = new HashMap<>(); + Map<String, String> analysisMap = new HashMap<>(); + + if (bootstrapMode != null) { + bootstrapMap.put(CoreProperties.ANALYSIS_MODE, bootstrapMode); + } + if (analysisMode != null) { + analysisMap.put(CoreProperties.ANALYSIS_MODE, analysisMode); + } + return new DefaultAnalysisMode(new GlobalProperties(bootstrapMap), new AnalysisProperties(analysisMap)); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchExtensionDictionnaryTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchExtensionDictionnaryTest.java new file mode 100644 index 00000000000..77a0021ef31 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchExtensionDictionnaryTest.java @@ -0,0 +1,412 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import com.google.common.collect.Lists; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import org.junit.Test; +import org.sonar.api.BatchExtension; +import org.sonar.api.batch.BuildBreaker; +import org.sonar.api.batch.CheckProject; +import org.sonar.api.batch.Decorator; +import org.sonar.api.batch.DependedUpon; +import org.sonar.api.batch.DependsUpon; +import org.sonar.api.batch.Phase; +import org.sonar.api.batch.PostJob; +import org.sonar.api.batch.Sensor; +import org.sonar.api.batch.SensorContext; +import org.sonar.api.batch.postjob.PostJobContext; +import org.sonar.api.resources.Project; +import org.sonar.batch.postjob.PostJobOptimizer; +import org.sonar.batch.sensor.DefaultSensorContext; +import org.sonar.batch.sensor.SensorOptimizer; +import org.sonar.core.platform.ComponentContainer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +public class BatchExtensionDictionnaryTest { + + private BatchExtensionDictionnary newSelector(Object... extensions) { + ComponentContainer iocContainer = new ComponentContainer(); + for (Object extension : extensions) { + iocContainer.addSingleton(extension); + } + return new BatchExtensionDictionnary(iocContainer, mock(DefaultSensorContext.class), mock(SensorOptimizer.class), mock(PostJobContext.class), + mock(PostJobOptimizer.class)); + } + + @Test + public void testGetFilteredExtensionWithExtensionMatcher() { + final Sensor sensor1 = new FakeSensor(); + final Sensor sensor2 = new FakeSensor(); + + BatchExtensionDictionnary selector = newSelector(sensor1, sensor2); + Collection<Sensor> sensors = selector.select(Sensor.class, null, true, new ExtensionMatcher() { + @Override + public boolean accept(Object extension) { + return extension.equals(sensor1); + } + }); + + assertThat(sensors).contains(sensor1); + assertEquals(1, sensors.size()); + } + + @Test + public void testGetFilteredExtensions() { + Sensor sensor1 = new FakeSensor(); + Sensor sensor2 = new FakeSensor(); + Decorator decorator = mock(Decorator.class); + + BatchExtensionDictionnary selector = newSelector(sensor1, sensor2, decorator); + Collection<Sensor> sensors = selector.select(Sensor.class, null, true, 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); + + BatchExtensionDictionnary dictionnary = new BatchExtensionDictionnary(child, mock(DefaultSensorContext.class), mock(SensorOptimizer.class), mock(PostJobContext.class), + mock(PostJobOptimizer.class)); + assertThat(dictionnary.select(Sensor.class, null, true, null)).containsOnly(a, b, c); + } + + @Test + public void sortExtensionsByDependency() { + BatchExtension a = new MethodDependentOf(null); + BatchExtension b = new MethodDependentOf(a); + BatchExtension c = new MethodDependentOf(b); + + BatchExtensionDictionnary selector = newSelector(b, c, a); + List<BatchExtension> extensions = Lists.newArrayList(selector.select(BatchExtension.class, null, 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() { + BatchExtension a = new GeneratesSomething("foo"); + BatchExtension b = new MethodDependentOf("foo"); + + BatchExtensionDictionnary selector = newSelector(a, b); + List<BatchExtension> extensions = Lists.newArrayList(selector.select(BatchExtension.class, null, 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(BatchExtension.class, null, true, null)); + + assertThat(extensions).hasSize(2); + assertThat(extensions.get(0)).isEqualTo(a); + assertThat(extensions.get(1)).isEqualTo(b); + } + + @Test + public void methodDependsUponCollection() { + BatchExtension a = new GeneratesSomething("foo"); + BatchExtension b = new MethodDependentOf(Arrays.asList("foo")); + + BatchExtensionDictionnary selector = newSelector(a, b); + List<BatchExtension> extensions = Lists.newArrayList(selector.select(BatchExtension.class, null, 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(BatchExtension.class, null, true, null)); + + assertThat(extensions).hasSize(2); + assertThat(extensions.get(0)).isEqualTo(a); + assertThat(extensions.get(1)).isEqualTo(b); + } + + @Test + public void methodDependsUponArray() { + BatchExtension a = new GeneratesSomething("foo"); + BatchExtension b = new MethodDependentOf(new String[] {"foo"}); + + BatchExtensionDictionnary selector = newSelector(a, b); + List<BatchExtension> extensions = Lists.newArrayList(selector.select(BatchExtension.class, null, 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(BatchExtension.class, null, true, null)); + + assertThat(extensions).hasSize(2); + assertThat(extensions.get(0)).isEqualTo(a); + assertThat(extensions.get(1)).isEqualTo(b); + } + + @Test + public void useClassAnnotationsToSortExtensions() { + BatchExtension a = new ClassDependedUpon(); + BatchExtension b = new ClassDependsUpon(); + + BatchExtensionDictionnary selector = newSelector(a, b); + List<BatchExtension> extensions = Lists.newArrayList(selector.select(BatchExtension.class, null, 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(BatchExtension.class, null, true, null)); + + assertThat(extensions).hasSize(2); + assertThat(extensions.get(0)).isEqualTo(a); + assertThat(extensions.get(1)).isEqualTo(b); + } + + @Test + public void useClassAnnotationsOnInterfaces() { + BatchExtension a = new InterfaceDependedUpon() { + }; + BatchExtension b = new InterfaceDependsUpon() { + }; + + BatchExtensionDictionnary selector = newSelector(a, b); + List<BatchExtension> extensions = Lists.newArrayList(selector.select(BatchExtension.class, null, 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(BatchExtension.class, null, true, null)); + + assertThat(extensions).hasSize(2); + assertThat(extensions.get(0)).isEqualTo(a); + assertThat(extensions.get(1)).isEqualTo(b); + } + + @Test + public void checkProject() { + BatchExtension ok = new CheckProjectOK(); + BatchExtension ko = new CheckProjectKO(); + + BatchExtensionDictionnary selector = newSelector(ok, ko); + List<BatchExtension> extensions = Lists.newArrayList(selector.select(BatchExtension.class, new Project("key"), true, null)); + + assertThat(extensions).hasSize(1); + assertThat(extensions.get(0)).isInstanceOf(CheckProjectOK.class); + } + + @Test + public void inheritAnnotations() { + BatchExtension a = new SubClass("foo"); + BatchExtension b = new MethodDependentOf("foo"); + + BatchExtensionDictionnary selector = newSelector(b, a); + List<BatchExtension> extensions = Lists.newArrayList(selector.select(BatchExtension.class, null, 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(BatchExtension.class, null, 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() { + BatchExtensionDictionnary selector = newSelector(); + BatchExtension wrong = new BatchExtension() { + @DependsUpon + private Object foo() { + return "foo"; + } + }; + selector.evaluateAnnotatedClasses(wrong, DependsUpon.class); + } + + @Test + public void dependsUponPhase() { + BatchExtension pre = new PreSensor(); + BatchExtension analyze = new GeneratesSomething("something"); + BatchExtension post = new PostSensor(); + + BatchExtensionDictionnary selector = newSelector(analyze, post, pre); + List extensions = Lists.newArrayList(selector.select(BatchExtension.class, null, true, null)); + + assertThat(extensions).hasSize(3); + assertThat(extensions.get(0)).isEqualTo(pre); + assertThat(extensions.get(1)).isEqualTo(analyze); + assertThat(extensions.get(2)).isEqualTo(post); + } + + @Test + public void dependsUponInheritedPhase() { + BatchExtension pre = new PreSensorSubclass(); + BatchExtension analyze = new GeneratesSomething("something"); + BatchExtension post = new PostSensorSubclass(); + + BatchExtensionDictionnary selector = newSelector(analyze, post, pre); + List extensions = Lists.newArrayList(selector.select(BatchExtension.class, null, true, null)); + + assertThat(extensions).hasSize(3); + assertThat(extensions.get(0)).isEqualTo(pre); + assertThat(extensions.get(1)).isEqualTo(analyze); + assertThat(extensions.get(2)).isEqualTo(post); + } + + @Test + public void buildStatusCheckersAreExecutedAfterOtherPostJobs() { + BuildBreaker checker = new BuildBreaker() { + public void executeOn(Project project, SensorContext context) { + } + }; + + BatchExtensionDictionnary selector = newSelector(new FakePostJob(), checker, new FakePostJob()); + List extensions = Lists.newArrayList(selector.select(PostJob.class, null, true, null)); + + assertThat(extensions).hasSize(3); + assertThat(extensions.get(2)).isEqualTo(checker); + } + + class FakeSensor implements Sensor { + + public void analyse(Project project, SensorContext context) { + + } + + public boolean shouldExecuteOnProject(Project project) { + return true; + } + } + + class MethodDependentOf implements BatchExtension { + private Object dep; + + MethodDependentOf(Object o) { + this.dep = o; + } + + @DependsUpon + public Object dependsUponObject() { + return dep; + } + } + + @DependsUpon("flag") + class ClassDependsUpon implements BatchExtension { + } + + @DependedUpon("flag") + class ClassDependedUpon implements BatchExtension { + } + + @DependsUpon("flag") + interface InterfaceDependsUpon extends BatchExtension { + } + + @DependedUpon("flag") + interface InterfaceDependedUpon extends BatchExtension { + } + + class GeneratesSomething implements BatchExtension { + private Object gen; + + GeneratesSomething(Object o) { + this.gen = o; + } + + @DependedUpon + public Object generates() { + return gen; + } + } + + class SubClass extends GeneratesSomething { + SubClass(Object o) { + super(o); + } + } + + @Phase(name = Phase.Name.PRE) + class PreSensor implements BatchExtension { + + } + + class PreSensorSubclass extends PreSensor { + + } + + @Phase(name = Phase.Name.POST) + class PostSensor implements BatchExtension { + + } + + class PostSensorSubclass extends PostSensor { + + } + + class CheckProjectOK implements BatchExtension, CheckProject { + public boolean shouldExecuteOnProject(Project project) { + return true; + } + } + + class CheckProjectKO implements BatchExtension, CheckProject { + public boolean shouldExecuteOnProject(Project project) { + return false; + } + } + + private class FakePostJob implements PostJob { + public void executeOn(Project project, SensorContext context) { + } + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java new file mode 100644 index 00000000000..95e17ca849a --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java @@ -0,0 +1,86 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import java.io.File; +import java.util.List; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.batch.cache.WSLoader; +import org.sonar.batch.cache.WSLoaderResult; +import org.sonar.core.platform.RemotePlugin; +import org.sonar.home.cache.FileCache; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class BatchPluginInstallerTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + FileCache fileCache = mock(FileCache.class); + BatchWsClient wsClient = mock(BatchWsClient.class); + BatchPluginPredicate pluginPredicate = mock(BatchPluginPredicate.class); + + @Test + public void listRemotePlugins() { + + WSLoader wsLoader = mock(WSLoader.class); + when(wsLoader.loadString("/deploy/plugins/index.txt")).thenReturn(new WSLoaderResult<>("checkstyle\nsqale", true)); + BatchPluginInstaller underTest = new BatchPluginInstaller(wsLoader, wsClient, fileCache, pluginPredicate); + + List<RemotePlugin> remotePlugins = underTest.listRemotePlugins(); + assertThat(remotePlugins).extracting("key").containsOnly("checkstyle", "sqale"); + } + + @Test + public void should_download_plugin() throws Exception { + File pluginJar = temp.newFile(); + when(fileCache.get(eq("checkstyle-plugin.jar"), eq("fakemd5_1"), any(FileCache.Downloader.class))).thenReturn(pluginJar); + + WSLoader wsLoader = mock(WSLoader.class); + BatchPluginInstaller underTest = new BatchPluginInstaller(wsLoader, wsClient, fileCache, pluginPredicate); + + RemotePlugin remote = new RemotePlugin("checkstyle").setFile("checkstyle-plugin.jar", "fakemd5_1"); + File file = underTest.download(remote); + + assertThat(file).isEqualTo(pluginJar); + } + + @Test + public void should_fail_to_get_plugin_index() { + thrown.expect(IllegalStateException.class); + + WSLoader wsLoader = mock(WSLoader.class); + doThrow(new IllegalStateException()).when(wsLoader).loadString("/deploy/plugins/index.txt"); + + new BatchPluginInstaller(wsLoader, wsClient, fileCache, pluginPredicate).installRemotes(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchPluginJarExploderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchPluginJarExploderTest.java new file mode 100644 index 00000000000..fe991f5d406 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchPluginJarExploderTest.java @@ -0,0 +1,80 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import java.io.File; +import java.io.IOException; +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.core.platform.ExplodedPlugin; +import org.sonar.core.platform.PluginInfo; +import org.sonar.home.cache.FileCache; +import org.sonar.home.cache.FileCacheBuilder; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BatchPluginJarExploderTest { + + @ClassRule + public static TemporaryFolder temp = new TemporaryFolder(); + + File userHome; + BatchPluginJarExploder underTest; + + @Before + public void setUp() throws IOException { + userHome = temp.newFolder(); + FileCache fileCache = new FileCacheBuilder(new Slf4jLogger()).setUserHome(userHome).build(); + underTest = new BatchPluginJarExploder(fileCache); + } + + @Test + public void copy_and_extract_libs() throws IOException { + File fileFromCache = getFileFromCache("sonar-checkstyle-plugin-2.8.jar"); + ExplodedPlugin exploded = underTest.explode(PluginInfo.create(fileFromCache)); + + assertThat(exploded.getKey()).isEqualTo("checkstyle"); + assertThat(exploded.getMain()).isFile().exists(); + assertThat(exploded.getLibs()).extracting("name").containsOnly("antlr-2.7.6.jar", "checkstyle-5.1.jar", "commons-cli-1.0.jar"); + assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar")).exists(); + assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar_unzip/META-INF/lib/checkstyle-5.1.jar")).exists(); + } + + @Test + public void extract_only_libs() throws IOException { + File fileFromCache = getFileFromCache("sonar-checkstyle-plugin-2.8.jar"); + underTest.explode(PluginInfo.create(fileFromCache)); + + assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar")).exists(); + assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar_unzip/META-INF/MANIFEST.MF")).doesNotExist(); + assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar_unzip/org/sonar/plugins/checkstyle/CheckstyleVersion.class")).doesNotExist(); + } + + File getFileFromCache(String filename) throws IOException { + File src = FileUtils.toFile(getClass().getResource(this.getClass().getSimpleName() + "/" + filename)); + File destFile = new File(new File(userHome, "" + filename.hashCode()), filename); + FileUtils.copyFile(src, destFile); + return destFile; + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java new file mode 100644 index 00000000000..0e8f29a1609 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java @@ -0,0 +1,103 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import org.junit.Test; +import org.sonar.api.CoreProperties; +import org.sonar.api.config.Settings; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class BatchPluginPredicateTest { + + Settings settings = new Settings(); + GlobalMode mode = mock(GlobalMode.class); + + @Test + public void accept_if_no_inclusions_nor_exclusions() { + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.getWhites()).isEmpty(); + assertThat(predicate.getBlacks()).isEmpty(); + assertThat(predicate.apply("pmd")).isTrue(); + assertThat(predicate.apply("buildbreaker")).isTrue(); + } + + @Test + public void exclude_buildbreaker_in_preview_mode() { + when(mode.isPreview()).thenReturn(true); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.apply("buildbreaker")).isFalse(); + } + + @Test + public void inclusions_take_precedence_over_exclusions() { + when(mode.isPreview()).thenReturn(true); + settings + .setProperty(CoreProperties.PREVIEW_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs") + .setProperty(CoreProperties.PREVIEW_EXCLUDE_PLUGINS, "cobertura,pmd"); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.apply("pmd")).isTrue(); + } + + @Test + public void verify_both_inclusions_and_exclusions() { + when(mode.isPreview()).thenReturn(true); + settings + .setProperty(CoreProperties.PREVIEW_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs") + .setProperty(CoreProperties.PREVIEW_EXCLUDE_PLUGINS, "cobertura"); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.apply("checkstyle")).isTrue(); + assertThat(predicate.apply("pmd")).isTrue(); + assertThat(predicate.apply("cobertura")).isFalse(); + } + + @Test + public void verify_both_inclusions_and_exclusions_issues() { + when(mode.isIssues()).thenReturn(true); + settings + .setProperty(CoreProperties.PREVIEW_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs") + .setProperty(CoreProperties.PREVIEW_EXCLUDE_PLUGINS, "cobertura"); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.apply("checkstyle")).isTrue(); + assertThat(predicate.apply("pmd")).isTrue(); + assertThat(predicate.apply("cobertura")).isFalse(); + } + + @Test + public void test_exclusions_without_any_inclusions() { + when(mode.isPreview()).thenReturn(true); + settings.setProperty(CoreProperties.PREVIEW_EXCLUDE_PLUGINS, "checkstyle,pmd,findbugs"); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.apply("checkstyle")).isFalse(); + assertThat(predicate.apply("pmd")).isFalse(); + assertThat(predicate.apply("cobertura")).isTrue(); + } + + @Test + public void trim_inclusions_and_exclusions() { + settings + .setProperty(CoreProperties.PREVIEW_INCLUDE_PLUGINS, "checkstyle, pmd, findbugs") + .setProperty(CoreProperties.PREVIEW_EXCLUDE_PLUGINS, "cobertura, pmd"); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.apply("pmd")).isTrue(); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchPluginRepositoryTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchPluginRepositoryTest.java new file mode 100644 index 00000000000..0d0fc068dfe --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchPluginRepositoryTest.java @@ -0,0 +1,76 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import com.google.common.collect.ImmutableMap; +import org.junit.Test; +import org.sonar.api.SonarPlugin; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginLoader; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.anyCollectionOf; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class BatchPluginRepositoryTest { + + PluginInstaller installer = mock(PluginInstaller.class); + PluginLoader loader = mock(PluginLoader.class); + BatchPluginRepository underTest = new BatchPluginRepository(installer, loader); + + @Test + public void install_and_load_plugins() { + PluginInfo info = new PluginInfo("squid"); + ImmutableMap<String, PluginInfo> infos = ImmutableMap.of("squid", info); + SonarPlugin instance = mock(SonarPlugin.class); + when(loader.load(infos)).thenReturn(ImmutableMap.of("squid", instance)); + when(installer.installRemotes()).thenReturn(infos); + + underTest.start(); + + assertThat(underTest.getPluginInfos()).containsOnly(info); + assertThat(underTest.getPluginInfo("squid")).isSameAs(info); + assertThat(underTest.getPluginInstance("squid")).isSameAs(instance); + + underTest.stop(); + verify(loader).unload(anyCollectionOf(SonarPlugin.class)); + } + + @Test + public void fail_if_requesting_missing_plugin() { + underTest.start(); + + try { + underTest.getPluginInfo("unknown"); + fail(); + } catch (IllegalStateException e) { + assertThat(e).hasMessage("Plugin [unknown] does not exist"); + } + try { + underTest.getPluginInstance("unknown"); + fail(); + } catch (IllegalStateException e) { + assertThat(e).hasMessage("Plugin [unknown] does not exist"); + } + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchWsClientProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchWsClientProviderTest.java new file mode 100644 index 00000000000..76fa07167ab --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchWsClientProviderTest.java @@ -0,0 +1,76 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import java.util.HashMap; +import java.util.Map; +import org.junit.Test; +import org.sonar.batch.bootstrapper.EnvironmentInformation; +import org.sonarqube.ws.client.HttpConnector; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BatchWsClientProviderTest { + + BatchWsClientProvider underTest = new BatchWsClientProvider(); + EnvironmentInformation env = new EnvironmentInformation("Maven Plugin", "2.3"); + + @Test + public void provide_client_with_default_settings() { + GlobalProperties settings = new GlobalProperties(new HashMap<String, String>()); + + BatchWsClient client = underTest.provide(settings, env); + + assertThat(client).isNotNull(); + assertThat(client.baseUrl()).isEqualTo("http://localhost:9000/"); + HttpConnector httpConnector = (HttpConnector) client.wsConnector(); + assertThat(httpConnector.baseUrl()).isEqualTo("http://localhost:9000/"); + assertThat(httpConnector.okHttpClient().getProxy()).isNull(); + assertThat(httpConnector.okHttpClient().getConnectTimeout()).isEqualTo(5_000); + assertThat(httpConnector.okHttpClient().getReadTimeout()).isEqualTo(60_000); + assertThat(httpConnector.userAgent()).isEqualTo("Maven Plugin/2.3"); + } + + @Test + public void provide_client_with_custom_settings() { + Map<String, String> props = new HashMap<>(); + props.put("sonar.host.url", "https://here/sonarqube"); + props.put("sonar.login", "theLogin"); + props.put("sonar.password", "thePassword"); + props.put("sonar.ws.timeout", "42"); + GlobalProperties settings = new GlobalProperties(props); + + BatchWsClient client = underTest.provide(settings, env); + + assertThat(client).isNotNull(); + HttpConnector httpConnector = (HttpConnector) client.wsConnector(); + assertThat(httpConnector.baseUrl()).isEqualTo("https://here/sonarqube/"); + assertThat(httpConnector.okHttpClient().getProxy()).isNull(); + assertThat(httpConnector.userAgent()).isEqualTo("Maven Plugin/2.3"); + } + + @Test + public void build_singleton() { + GlobalProperties settings = new GlobalProperties(new HashMap<String, String>()); + BatchWsClient first = underTest.provide(settings, env); + BatchWsClient second = underTest.provide(settings, env); + assertThat(first).isSameAs(second); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchWsClientTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchWsClientTest.java new file mode 100644 index 00000000000..3689c2d601a --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchWsClientTest.java @@ -0,0 +1,116 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import java.util.List; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Mockito; +import org.sonar.api.utils.MessageException; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.MockWsResponse; +import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.WsRequest; +import org.sonarqube.ws.client.WsResponse; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class BatchWsClientTest { + + @Rule + public LogTester logTester = new LogTester(); + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + WsClient wsClient = mock(WsClient.class, Mockito.RETURNS_DEEP_STUBS); + + @Test + public void log_and_profile_request_if_debug_level() throws Exception { + WsRequest request = newRequest(); + WsResponse response = newResponse().setRequestUrl("https://local/api/issues/search"); + when(wsClient.wsConnector().call(request)).thenReturn(response); + + logTester.setLevel(LoggerLevel.DEBUG); + BatchWsClient underTest = new BatchWsClient(wsClient, false); + + WsResponse result = underTest.call(request); + + // do not fail the execution -> interceptor returns the response + assertThat(result).isSameAs(response); + + // check logs + List<String> debugLogs = logTester.logs(LoggerLevel.DEBUG); + assertThat(debugLogs).hasSize(1); + assertThat(debugLogs.get(0)).contains("GET 200 https://local/api/issues/search | time="); + } + + @Test + public void fail_if_requires_credentials() throws Exception { + expectedException.expect(MessageException.class); + expectedException + .expectMessage("Not authorized. Analyzing this project requires to be authenticated. Please provide the values of the properties sonar.login and sonar.password."); + + WsRequest request = newRequest(); + WsResponse response = newResponse().setCode(401); + when(wsClient.wsConnector().call(request)).thenReturn(response); + + new BatchWsClient(wsClient, false).call(request); + } + + @Test + public void fail_if_credentials_are_not_valid() throws Exception { + expectedException.expect(MessageException.class); + expectedException.expectMessage("Not authorized. Please check the properties sonar.login and sonar.password."); + + WsRequest request = newRequest(); + WsResponse response = newResponse().setCode(401); + when(wsClient.wsConnector().call(request)).thenReturn(response); + + new BatchWsClient(wsClient, /* credentials are configured */true).call(request); + } + + @Test + public void fail_if_requires_permission() throws Exception { + expectedException.expect(MessageException.class); + expectedException.expectMessage("missing scan permission, missing another permission"); + + WsRequest request = newRequest(); + WsResponse response = newResponse() + .setCode(403) + .setContent("{\"errors\":[{\"msg\":\"missing scan permission\"}, {\"msg\":\"missing another permission\"}]}"); + when(wsClient.wsConnector().call(request)).thenReturn(response); + + new BatchWsClient(wsClient, true).call(request); + } + + private MockWsResponse newResponse() { + return new MockWsResponse().setRequestUrl("https://local/api/issues/search"); + } + + private WsRequest newRequest() { + return new GetRequest("api/issues/search"); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/DroppedPropertyCheckerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/DroppedPropertyCheckerTest.java new file mode 100644 index 00000000000..e6cd758d253 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/DroppedPropertyCheckerTest.java @@ -0,0 +1,62 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import com.google.common.collect.ImmutableMap; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DroppedPropertyCheckerTest { + private static final String SOME_VALUE = "some value"; + private static final String DROPPED_PROPERTY_1 = "I'm dropped"; + private static final String DROPPED_PROPERTY_MSG_1 = "blablabla!"; + + @Rule + public LogTester logTester = new LogTester(); + + @Test + public void no_log_if_no_dropped_property() { + new DroppedPropertyChecker(ImmutableMap.of(DROPPED_PROPERTY_1, SOME_VALUE), ImmutableMap.<String, String>of()).checkDroppedProperties(); + + assertThat(logTester.logs()).isEmpty(); + } + + @Test + public void no_log_if_settings_does_not_contain_any_dropped_property() { + new DroppedPropertyChecker(ImmutableMap.<String, String>of(), ImmutableMap.of(DROPPED_PROPERTY_1, DROPPED_PROPERTY_MSG_1)).checkDroppedProperties(); + + assertThat(logTester.logs()).isEmpty(); + } + + @Test + public void warn_log_if_settings_contains_any_dropped_property() { + new DroppedPropertyChecker(ImmutableMap.of(DROPPED_PROPERTY_1, SOME_VALUE), ImmutableMap.of(DROPPED_PROPERTY_1, DROPPED_PROPERTY_MSG_1)).checkDroppedProperties(); + + assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty(); + assertThat(logTester.logs(LoggerLevel.WARN)).containsOnly("Property '" + DROPPED_PROPERTY_1 + "' is not supported any more. " + DROPPED_PROPERTY_MSG_1); + assertThat(logTester.logs(LoggerLevel.INFO)).isEmpty(); + assertThat(logTester.logs(LoggerLevel.DEBUG)).isEmpty(); + assertThat(logTester.logs(LoggerLevel.TRACE)).isEmpty(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/ExtensionInstallerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/ExtensionInstallerTest.java new file mode 100644 index 00000000000..c368fb146b6 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/ExtensionInstallerTest.java @@ -0,0 +1,136 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import java.util.Arrays; +import java.util.List; +import org.apache.commons.lang.ClassUtils; +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.BatchExtension; +import org.sonar.api.ExtensionProvider; +import org.sonar.api.SonarPlugin; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.core.platform.ComponentContainer; +import org.sonar.core.platform.PluginInfo; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ExtensionInstallerTest { + + GlobalMode mode; + BatchPluginRepository pluginRepository = mock(BatchPluginRepository.class); + + private static SonarPlugin newPluginInstance(final Object... extensions) { + return new SonarPlugin() { + public List getExtensions() { + return Arrays.asList(extensions); + } + }; + } + + @Before + public void setUp() { + mode = mock(GlobalMode.class); + } + + @Test + public void should_filter_extensions_to_install() { + when(pluginRepository.getPluginInfos()).thenReturn(Arrays.asList(new PluginInfo("foo"))); + when(pluginRepository.getPluginInstance("foo")).thenReturn(newPluginInstance(Foo.class, Bar.class)); + + ComponentContainer container = new ComponentContainer(); + ExtensionInstaller installer = new ExtensionInstaller(pluginRepository, mock(AnalysisMode.class)); + installer.install(container, new FooMatcher()); + + assertThat(container.getComponentByType(Foo.class)).isNotNull(); + assertThat(container.getComponentByType(Bar.class)).isNull(); + } + + @Test + public void should_execute_extension_provider() { + when(pluginRepository.getPluginInfos()).thenReturn(Arrays.asList(new PluginInfo("foo"))); + when(pluginRepository.getPluginInstance("foo")).thenReturn(newPluginInstance(new FooProvider(), new BarProvider())); + ComponentContainer container = new ComponentContainer(); + ExtensionInstaller installer = new ExtensionInstaller(pluginRepository, mock(AnalysisMode.class)); + + installer.install(container, new FooMatcher()); + + assertThat(container.getComponentByType(Foo.class)).isNotNull(); + assertThat(container.getComponentByType(Bar.class)).isNull(); + } + + @Test + public void should_provide_list_of_extensions() { + when(pluginRepository.getPluginInfos()).thenReturn(Arrays.asList(new PluginInfo("foo"))); + when(pluginRepository.getPluginInstance("foo")).thenReturn(newPluginInstance(new FooBarProvider())); + ComponentContainer container = new ComponentContainer(); + ExtensionInstaller installer = new ExtensionInstaller(pluginRepository, mock(AnalysisMode.class)); + + installer.install(container, new TrueMatcher()); + + assertThat(container.getComponentByType(Foo.class)).isNotNull(); + assertThat(container.getComponentByType(Bar.class)).isNotNull(); + } + + private static class FooMatcher implements ExtensionMatcher { + public boolean accept(Object extension) { + return extension.equals(Foo.class) || ClassUtils.isAssignable(Foo.class, extension.getClass()) || ClassUtils.isAssignable(FooProvider.class, extension.getClass()); + } + } + + private static class TrueMatcher implements ExtensionMatcher { + public boolean accept(Object extension) { + return true; + } + } + + public static class Foo implements BatchExtension { + + } + + public static class Bar implements BatchExtension { + + } + + public static class FooProvider extends ExtensionProvider implements BatchExtension { + @Override + public Object provide() { + return new Foo(); + } + } + + public static class BarProvider extends ExtensionProvider implements BatchExtension { + @Override + public Object provide() { + return new Bar(); + } + } + + public static class FooBarProvider extends ExtensionProvider implements BatchExtension { + @Override + public Object provide() { + return Arrays.asList(new Foo(), new Bar()); + } + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/ExtensionUtilsTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/ExtensionUtilsTest.java new file mode 100644 index 00000000000..f83920847e3 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/ExtensionUtilsTest.java @@ -0,0 +1,88 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import org.junit.Test; +import org.sonar.api.BatchComponent; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.InstantiationStrategy; +import org.sonar.api.server.ServerSide; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ExtensionUtilsTest { + + @Test + public void shouldBeBatchInstantiationStrategy() { + assertThat(ExtensionUtils.isInstantiationStrategy(BatchService.class, InstantiationStrategy.PER_BATCH)).isTrue(); + assertThat(ExtensionUtils.isInstantiationStrategy(new BatchService(), InstantiationStrategy.PER_BATCH)).isTrue(); + assertThat(ExtensionUtils.isInstantiationStrategy(ProjectService.class, InstantiationStrategy.PER_BATCH)).isFalse(); + assertThat(ExtensionUtils.isInstantiationStrategy(new ProjectService(), InstantiationStrategy.PER_BATCH)).isFalse(); + assertThat(ExtensionUtils.isInstantiationStrategy(DefaultService.class, InstantiationStrategy.PER_BATCH)).isFalse(); + assertThat(ExtensionUtils.isInstantiationStrategy(new DefaultService(), InstantiationStrategy.PER_BATCH)).isFalse(); + } + + @Test + public void shouldBeProjectInstantiationStrategy() { + assertThat(ExtensionUtils.isInstantiationStrategy(BatchService.class, InstantiationStrategy.PER_PROJECT)).isFalse(); + assertThat(ExtensionUtils.isInstantiationStrategy(new BatchService(), InstantiationStrategy.PER_PROJECT)).isFalse(); + assertThat(ExtensionUtils.isInstantiationStrategy(ProjectService.class, InstantiationStrategy.PER_PROJECT)).isTrue(); + assertThat(ExtensionUtils.isInstantiationStrategy(new ProjectService(), InstantiationStrategy.PER_PROJECT)).isTrue(); + assertThat(ExtensionUtils.isInstantiationStrategy(DefaultService.class, InstantiationStrategy.PER_PROJECT)).isTrue(); + assertThat(ExtensionUtils.isInstantiationStrategy(new DefaultService(), InstantiationStrategy.PER_PROJECT)).isTrue(); + } + + @Test + public void testIsBatchSide() { + assertThat(ExtensionUtils.isBatchSide(BatchService.class)).isTrue(); + assertThat(ExtensionUtils.isBatchSide(new BatchService())).isTrue(); + assertThat(ExtensionUtils.isBatchSide(DeprecatedBatchService.class)).isTrue(); + + assertThat(ExtensionUtils.isBatchSide(ServerService.class)).isFalse(); + assertThat(ExtensionUtils.isBatchSide(new ServerService())).isFalse(); + } + + @BatchSide + @InstantiationStrategy(InstantiationStrategy.PER_BATCH) + public static class BatchService { + + } + + public static class DeprecatedBatchService implements BatchComponent { + + } + + @BatchSide + @InstantiationStrategy(InstantiationStrategy.PER_PROJECT) + public static class ProjectService { + + } + + @BatchSide + public static class DefaultService { + + } + + @ServerSide + public static class ServerService { + + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/FileCacheProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/FileCacheProviderTest.java new file mode 100644 index 00000000000..09ce7835e0c --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/FileCacheProviderTest.java @@ -0,0 +1,65 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import java.io.File; +import java.io.IOException; + +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; +import org.junit.Test; +import org.sonar.api.config.Settings; +import org.sonar.home.cache.FileCache; +import static org.assertj.core.api.Assertions.assertThat; + +public class FileCacheProviderTest { + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void provide() { + FileCacheProvider provider = new FileCacheProvider(); + FileCache cache = provider.provide(new Settings()); + + assertThat(cache).isNotNull(); + assertThat(cache.getDir()).isNotNull().exists(); + } + + @Test + public void keep_singleton_instance() { + FileCacheProvider provider = new FileCacheProvider(); + Settings settings = new Settings(); + FileCache cache1 = provider.provide(settings); + FileCache cache2 = provider.provide(settings); + + assertThat(cache1).isSameAs(cache2); + } + + @Test + public void honor_sonarUserHome() throws IOException { + FileCacheProvider provider = new FileCacheProvider(); + Settings settings = new Settings(); + File f = temp.newFolder(); + settings.appendProperty("sonar.userHome", f.getAbsolutePath()); + FileCache cache = provider.provide(settings); + + assertThat(cache.getDir()).isEqualTo(new File(f, "cache")); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/GlobalContainerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/GlobalContainerTest.java new file mode 100644 index 00000000000..41152b3ccb5 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/GlobalContainerTest.java @@ -0,0 +1,76 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.utils.TempFolder; +import org.sonar.core.util.UuidFactory; + +import static org.assertj.core.api.Assertions.assertThat; + +public class GlobalContainerTest { + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private GlobalContainer createContainer(List<Object> extensions) { + Map<String, String> props = ImmutableMap.of(CoreProperties.WORKING_DIRECTORY, temp.getRoot().getAbsolutePath(), + CoreProperties.GLOBAL_WORKING_DIRECTORY, temp.getRoot().getAbsolutePath()); + + GlobalContainer container = GlobalContainer.create(props, extensions, false); + container.doBeforeStart(); + return container; + } + + @Test + public void should_add_components() { + GlobalContainer container = createContainer(Collections.emptyList()); + + assertThat(container.getComponentByType(UuidFactory.class)).isNotNull(); + assertThat(container.getComponentByType(TempFolder.class)).isNotNull(); + } + + @Test + public void should_add_bootstrap_extensions() { + GlobalContainer container = createContainer(Lists.newArrayList(Foo.class, new Bar())); + + assertThat(container.getComponentByType(Foo.class)).isNotNull(); + assertThat(container.getComponentByType(Bar.class)).isNotNull(); + } + + @BatchSide + public static class Foo { + + } + + @BatchSide + public static class Bar { + + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/GlobalModeTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/GlobalModeTest.java new file mode 100644 index 00000000000..5da1858e33b --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/GlobalModeTest.java @@ -0,0 +1,89 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import org.junit.Rule; +import org.junit.rules.ExpectedException; +import org.sonar.api.CoreProperties; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class GlobalModeTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void testModeNotSupported() { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("[preview, publish, issues]"); + + createMode(CoreProperties.ANALYSIS_MODE, "invalid"); + } + + @Test + public void testOtherProperty() { + GlobalMode mode = createMode(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_PUBLISH); + assertThat(mode.isPreview()).isFalse(); + assertThat(mode.isIssues()).isFalse(); + assertThat(mode.isPublish()).isTrue(); + } + + @Test + public void testIssuesMode() { + GlobalMode mode = createMode(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_ISSUES); + assertThat(mode.isPreview()).isFalse(); + assertThat(mode.isIssues()).isTrue(); + assertThat(mode.isPublish()).isFalse(); + } + + @Test + public void preview_mode_fallback_issues() { + GlobalMode mode = createMode(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_PREVIEW); + + assertThat(mode.isIssues()).isTrue(); + assertThat(mode.isPreview()).isFalse(); + } + + @Test + public void testDefault() { + GlobalMode mode = createMode(null, null); + assertThat(mode.isPreview()).isFalse(); + assertThat(mode.isIssues()).isFalse(); + assertThat(mode.isPublish()).isTrue(); + } + + @Test(expected = IllegalStateException.class) + public void testInvalidMode() { + createMode(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_ANALYSIS); + } + + private GlobalMode createMode(String key, String value) { + Map<String, String> map = new HashMap<>(); + if (key != null) { + map.put(key, value); + } + GlobalProperties props = new GlobalProperties(map); + return new GlobalMode(props); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/GlobalPropertiesTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/GlobalPropertiesTest.java new file mode 100644 index 00000000000..4f3fd5cebca --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/GlobalPropertiesTest.java @@ -0,0 +1,43 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import com.google.common.collect.Maps; +import org.junit.Test; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +public class GlobalPropertiesTest { + @Test + public void test_copy_of_properties() { + Map<String, String> map = Maps.newHashMap(); + map.put("foo", "bar"); + + GlobalProperties wrapper = new GlobalProperties(map); + assertThat(wrapper.properties()).containsOnly(entry("foo", "bar")); + assertThat(wrapper.properties()).isNotSameAs(map); + + map.put("put", "after_copy"); + assertThat(wrapper.properties()).hasSize(1); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/GlobalSettingsTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/GlobalSettingsTest.java new file mode 100644 index 00000000000..aa13b14ccb5 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/GlobalSettingsTest.java @@ -0,0 +1,79 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import java.util.Collections; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.config.PropertyDefinitions; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; +import org.sonar.scanner.protocol.input.GlobalRepositories; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class GlobalSettingsTest { + + public static final String SOME_VALUE = "some_value"; + @Rule + public ExpectedException thrown = ExpectedException.none(); + @Rule + public LogTester logTester = new LogTester(); + + GlobalRepositories globalRef; + GlobalProperties bootstrapProps; + + private GlobalMode mode; + + @Before + public void prepare() { + globalRef = new GlobalRepositories(); + bootstrapProps = new GlobalProperties(Collections.<String, String>emptyMap()); + mode = mock(GlobalMode.class); + } + + @Test + public void should_load_global_settings() { + globalRef.globalSettings().put("sonar.cpd.cross", "true"); + + GlobalSettings batchSettings = new GlobalSettings(bootstrapProps, new PropertyDefinitions(), globalRef, mode); + + assertThat(batchSettings.getBoolean("sonar.cpd.cross")).isTrue(); + } + + @Test + public void should_log_warn_msg_for_each_jdbc_property_if_present() { + globalRef.globalSettings().put("sonar.jdbc.url", SOME_VALUE); + globalRef.globalSettings().put("sonar.jdbc.username", SOME_VALUE); + globalRef.globalSettings().put("sonar.jdbc.password", SOME_VALUE); + + new GlobalSettings(bootstrapProps, new PropertyDefinitions(), globalRef, mode); + + assertThat(logTester.logs(LoggerLevel.WARN)).containsOnly( + "Property 'sonar.jdbc.url' is not supported any more. It will be ignored. There is no longer any DB connection to the SQ database.", + "Property 'sonar.jdbc.username' is not supported any more. It will be ignored. There is no longer any DB connection to the SQ database.", + "Property 'sonar.jdbc.password' is not supported any more. It will be ignored. There is no longer any DB connection to the SQ database." + ); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/GlobalTempFolderProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/GlobalTempFolderProviderTest.java new file mode 100644 index 00000000000..e0d4ee9d3ea --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/GlobalTempFolderProviderTest.java @@ -0,0 +1,142 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import org.sonar.api.utils.System2; +import org.apache.commons.io.FileUtils; +import org.sonar.api.utils.TempFolder; +import com.google.common.collect.ImmutableMap; +import org.sonar.api.CoreProperties; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.FileTime; +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.assertj.core.api.Assertions.assertThat; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class GlobalTempFolderProviderTest { + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private GlobalTempFolderProvider tempFolderProvider = new GlobalTempFolderProvider(); + + @Test + public void createTempFolderProps() throws Exception { + File workingDir = temp.newFolder(); + + TempFolder tempFolder = tempFolderProvider.provide(new GlobalProperties(ImmutableMap.of(CoreProperties.GLOBAL_WORKING_DIRECTORY, workingDir.getAbsolutePath()))); + tempFolder.newDir(); + tempFolder.newFile(); + assertThat(getCreatedTempDir(workingDir)).exists(); + assertThat(getCreatedTempDir(workingDir).list()).hasSize(2); + + FileUtils.deleteQuietly(workingDir); + } + + @Test + public void cleanUpOld() throws IOException { + long creationTime = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(100); + File workingDir = temp.newFolder(); + + for (int i = 0; i < 3; i++) { + File tmp = new File(workingDir, ".sonartmp_" + i); + tmp.mkdirs(); + setFileCreationDate(tmp, creationTime); + } + + tempFolderProvider.provide(new GlobalProperties(ImmutableMap.of(CoreProperties.GLOBAL_WORKING_DIRECTORY, workingDir.getAbsolutePath()))); + // this also checks that all other temps were deleted + assertThat(getCreatedTempDir(workingDir)).exists(); + + FileUtils.deleteQuietly(workingDir); + } + + @Test + public void createTempFolderSonarHome() throws Exception { + // with sonar home, it will be in {sonar.home}/.sonartmp + File sonarHome = temp.newFolder(); + File workingDir = new File(sonarHome, CoreProperties.GLOBAL_WORKING_DIRECTORY_DEFAULT_VALUE).getAbsoluteFile(); + + TempFolder tempFolder = tempFolderProvider.provide(new GlobalProperties(ImmutableMap.of("sonar.userHome", sonarHome.getAbsolutePath()))); + tempFolder.newDir(); + tempFolder.newFile(); + assertThat(getCreatedTempDir(workingDir)).exists(); + assertThat(getCreatedTempDir(workingDir).list()).hasSize(2); + + FileUtils.deleteQuietly(sonarHome); + } + + @Test + public void createTempFolderDefault() throws Exception { + System2 system = mock(System2.class); + tempFolderProvider = new GlobalTempFolderProvider(system); + File userHome = temp.newFolder(); + + when(system.envVariable("SONAR_USER_HOME")).thenReturn(null); + when(system.property("user.home")).thenReturn(userHome.getAbsolutePath().toString()); + + // if nothing is defined, it will be in {user.home}/.sonar/.sonartmp + File defaultSonarHome = new File(userHome.getAbsolutePath(), ".sonar"); + File workingDir = new File(defaultSonarHome, CoreProperties.GLOBAL_WORKING_DIRECTORY_DEFAULT_VALUE).getAbsoluteFile(); + try { + TempFolder tempFolder = tempFolderProvider.provide(new GlobalProperties(Collections.<String, String>emptyMap())); + tempFolder.newDir(); + tempFolder.newFile(); + assertThat(getCreatedTempDir(workingDir)).exists(); + assertThat(getCreatedTempDir(workingDir).list()).hasSize(2); + } finally { + FileUtils.deleteQuietly(workingDir); + } + } + + @Test + public void dotWorkingDir() throws IOException { + File sonarHome = temp.getRoot(); + String globalWorkDir = "."; + GlobalProperties globalProperties = new GlobalProperties(ImmutableMap.of("sonar.userHome", sonarHome.getAbsolutePath(), + CoreProperties.GLOBAL_WORKING_DIRECTORY, globalWorkDir)); + + TempFolder tempFolder = tempFolderProvider.provide(globalProperties); + File newFile = tempFolder.newFile(); + assertThat(newFile.getParentFile().getParentFile().getAbsolutePath()).isEqualTo(sonarHome.getAbsolutePath()); + assertThat(newFile.getParentFile().getName()).startsWith(".sonartmp_"); + } + + private File getCreatedTempDir(File workingDir) { + assertThat(workingDir).isDirectory(); + assertThat(workingDir.listFiles()).hasSize(1); + return workingDir.listFiles()[0]; + } + + private void setFileCreationDate(File f, long time) throws IOException { + BasicFileAttributeView attributes = Files.getFileAttributeView(f.toPath(), BasicFileAttributeView.class); + FileTime creationTime = FileTime.fromMillis(time); + attributes.setTimes(creationTime, creationTime, creationTime); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/MetricProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/MetricProviderTest.java new file mode 100644 index 00000000000..8d1ab306b8d --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/MetricProviderTest.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import org.junit.Test; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Metric; +import org.sonar.api.measures.Metrics; + +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MetricProviderTest { + @Test + public void should_provide_at_least_core_metrics() { + MetricProvider provider = new MetricProvider(); + List<Metric> metrics = provider.provide(); + + assertThat(metrics).hasSize(CoreMetrics.getMetrics().size()); + assertThat(metrics).extracting("key").contains("ncloc"); + } + + @Test + public void should_provide_plugin_metrics() { + Metrics factory = new Metrics() { + public List<Metric> getMetrics() { + return Arrays.<Metric>asList(new Metric.Builder("custom", "Custom", Metric.ValueType.FLOAT).create()); + } + }; + MetricProvider provider = new MetricProvider(new Metrics[] {factory}); + List<Metric> metrics = provider.provide(); + + assertThat(metrics.size()).isEqualTo(1 + CoreMetrics.getMetrics().size()); + assertThat(metrics).extracting("key").contains("custom"); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/MockHttpServer.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/MockHttpServer.java new file mode 100644 index 00000000000..99f10de2c9e --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/MockHttpServer.java @@ -0,0 +1,118 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.io.IOUtils; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import static javax.servlet.http.HttpServletResponse.SC_OK; +import static org.apache.commons.io.IOUtils.write; + +public class MockHttpServer { + private Server server; + private String responseBody; + private String requestBody; + private String mockResponseData; + private int mockResponseStatus = SC_OK; + private List<String> targets = new ArrayList<>(); + + public void start() throws Exception { + server = new Server(0); + server.setHandler(getMockHandler()); + server.start(); + } + + public int getNumberRequests() { + return targets.size(); + } + + /** + * Creates an {@link org.mortbay.jetty.handler.AbstractHandler handler} returning an arbitrary String as a response. + * + * @return never <code>null</code>. + */ + public Handler getMockHandler() { + Handler handler = new AbstractHandler() { + + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + targets.add(target); + setResponseBody(getMockResponseData()); + setRequestBody(IOUtils.toString(baseRequest.getInputStream())); + response.setStatus(mockResponseStatus); + response.setContentType("text/xml;charset=utf-8"); + write(getResponseBody(), response.getOutputStream()); + baseRequest.setHandled(true); + } + }; + return handler; + } + + public void stop() { + try { + if (server != null) { + server.stop(); + } + } catch (Exception e) { + throw new IllegalStateException("Fail to stop HTTP server", e); + } + } + + public String getResponseBody() { + return responseBody; + } + + public void setResponseBody(String responseBody) { + this.responseBody = responseBody; + } + + public String getRequestBody() { + return requestBody; + } + + public void setRequestBody(String requestBody) { + this.requestBody = requestBody; + } + + public void setMockResponseStatus(int status) { + this.mockResponseStatus = status; + } + + public String getMockResponseData() { + return mockResponseData; + } + + public void setMockResponseData(String mockResponseData) { + this.mockResponseData = mockResponseData; + } + + public int getPort() { + return server.getConnectors()[0].getLocalPort(); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrapper/BatchTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrapper/BatchTest.java new file mode 100644 index 00000000000..b9696631f89 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrapper/BatchTest.java @@ -0,0 +1,73 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrapper; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; + +public class BatchTest { + @Test + public void testBuilder() { + Batch batch = newBatch(); + assertNotNull(batch); + + } + + private Batch newBatch() { + return Batch.builder() + .setEnvironment(new EnvironmentInformation("Gradle", "1.0")) + .addComponent("fake") + .build(); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailIfNullComponents() { + Batch.builder() + .setEnvironment(new EnvironmentInformation("Gradle", "1.0")) + .setComponents(null) + .build(); + } + + @Test + public void shouldDisableLoggingConfiguration() { + Batch batch = Batch.builder() + .setEnvironment(new EnvironmentInformation("Gradle", "1.0")) + .addComponent("fake") + .setEnableLoggingConfiguration(false) + .build(); + assertNull(batch.getLoggingConfiguration()); + } + + @Test + public void loggingConfigurationShouldBeEnabledByDefault() { + assertNotNull(newBatch().getLoggingConfiguration()); + } + + @Test + public void shoudSetLogListener() { + LogOutput logOutput = mock(LogOutput.class); + Batch batch = Batch.builder().setLogOutput(logOutput).build(); + assertThat(batch.getLoggingConfiguration().getLogOutput()).isEqualTo(logOutput); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrapper/EnvironmentInformationTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrapper/EnvironmentInformationTest.java new file mode 100644 index 00000000000..71dd0495d49 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrapper/EnvironmentInformationTest.java @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrapper; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class EnvironmentInformationTest { + @Test + public void test_bean() { + EnvironmentInformation env = new EnvironmentInformation("Maven Plugin", "2.0"); + + assertThat(env.getKey()).isEqualTo("Maven Plugin"); + assertThat(env.getVersion()).isEqualTo("2.0"); + } + + @Test + public void test_toString() { + EnvironmentInformation env = new EnvironmentInformation("Maven Plugin", "2.0"); + + assertThat(env.toString()).isEqualTo("Maven Plugin/2.0"); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrapper/LogCallbackAppenderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrapper/LogCallbackAppenderTest.java new file mode 100644 index 00000000000..6b225306fe0 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrapper/LogCallbackAppenderTest.java @@ -0,0 +1,76 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrapper; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.spi.ILoggingEvent; +import org.junit.Before; +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +public class LogCallbackAppenderTest { + private LogOutput listener; + private LogCallbackAppender appender; + private ILoggingEvent event; + + @Before + public void setUp() { + listener = mock(LogOutput.class); + appender = new LogCallbackAppender(listener); + } + + @Test + public void testLevelTranslation() { + testMessage("test", Level.INFO, LogOutput.Level.INFO); + testMessage("test", Level.DEBUG, LogOutput.Level.DEBUG); + testMessage("test", Level.ERROR, LogOutput.Level.ERROR); + testMessage("test", Level.TRACE, LogOutput.Level.TRACE); + testMessage("test", Level.WARN, LogOutput.Level.WARN); + + // this should never happen + testMessage("test", Level.OFF, LogOutput.Level.DEBUG); + } + + private void testMessage(String msg, Level level, LogOutput.Level translatedLevel) { + reset(listener); + event = mock(ILoggingEvent.class); + when(event.getFormattedMessage()).thenReturn(msg); + when(event.getLevel()).thenReturn(level); + + appender.append(event); + + verify(event).getFormattedMessage(); + verify(event).getLevel(); + verify(listener).log(msg, translatedLevel); + verifyNoMoreInteractions(event, listener); + } + + @Test + public void testChangeTarget() { + listener = mock(LogOutput.class); + appender.setTarget(listener); + testLevelTranslation(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrapper/LoggingConfigurationTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrapper/LoggingConfigurationTest.java new file mode 100644 index 00000000000..920af566e93 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrapper/LoggingConfigurationTest.java @@ -0,0 +1,167 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrapper; + +import com.google.common.collect.Maps; +import java.util.Map; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class LoggingConfigurationTest { + + @Test + public void testSetVerbose() { + assertThat(new LoggingConfiguration(null).setVerbose(true) + .getSubstitutionVariable(LoggingConfiguration.PROPERTY_ROOT_LOGGER_LEVEL)).isEqualTo(LoggingConfiguration.LEVEL_ROOT_VERBOSE); + + assertThat(new LoggingConfiguration(null).setVerbose(false) + .getSubstitutionVariable(LoggingConfiguration.PROPERTY_ROOT_LOGGER_LEVEL)).isEqualTo(LoggingConfiguration.LEVEL_ROOT_DEFAULT); + + assertThat(new LoggingConfiguration(null).setRootLevel("ERROR") + .getSubstitutionVariable(LoggingConfiguration.PROPERTY_ROOT_LOGGER_LEVEL)).isEqualTo("ERROR"); + } + + @Test + public void testSetVerboseAnalysis() { + Map<String, String> globalProps = Maps.newHashMap(); + LoggingConfiguration conf = new LoggingConfiguration(null).setProperties(globalProps); + assertThat(conf.getSubstitutionVariable(LoggingConfiguration.PROPERTY_ROOT_LOGGER_LEVEL)).isEqualTo(LoggingConfiguration.LEVEL_ROOT_DEFAULT); + assertThat(conf.getSubstitutionVariable(LoggingConfiguration.PROPERTY_SQL_LOGGER_LEVEL)).isEqualTo("WARN"); + + Map<String, String> analysisProperties = Maps.newHashMap(); + analysisProperties.put("sonar.verbose", "true"); + + conf.setProperties(analysisProperties, globalProps); + assertThat(conf.getSubstitutionVariable(LoggingConfiguration.PROPERTY_ROOT_LOGGER_LEVEL)).isEqualTo(LoggingConfiguration.LEVEL_ROOT_VERBOSE); + assertThat(conf.getSubstitutionVariable(LoggingConfiguration.PROPERTY_SQL_LOGGER_LEVEL)).isEqualTo("WARN"); + } + + @Test + public void testOverrideVerbose() { + Map<String, String> globalProps = Maps.newHashMap(); + globalProps.put("sonar.verbose", "true"); + LoggingConfiguration conf = new LoggingConfiguration(null).setProperties(globalProps); + assertThat(conf.getSubstitutionVariable(LoggingConfiguration.PROPERTY_ROOT_LOGGER_LEVEL)).isEqualTo(LoggingConfiguration.LEVEL_ROOT_VERBOSE); + assertThat(conf.getSubstitutionVariable(LoggingConfiguration.PROPERTY_SQL_LOGGER_LEVEL)).isEqualTo("WARN"); + + Map<String, String> analysisProperties = Maps.newHashMap(); + analysisProperties.put("sonar.verbose", "false"); + + conf.setProperties(analysisProperties, globalProps); + assertThat(conf.getSubstitutionVariable(LoggingConfiguration.PROPERTY_ROOT_LOGGER_LEVEL)).isEqualTo(LoggingConfiguration.LEVEL_ROOT_DEFAULT); + assertThat(conf.getSubstitutionVariable(LoggingConfiguration.PROPERTY_SQL_LOGGER_LEVEL)).isEqualTo("WARN"); + } + + @Test + public void shouldNotBeVerboseByDefault() { + assertThat(new LoggingConfiguration(null) + .getSubstitutionVariable(LoggingConfiguration.PROPERTY_ROOT_LOGGER_LEVEL)).isEqualTo(LoggingConfiguration.LEVEL_ROOT_DEFAULT); + } + + @Test + public void test_log_listener_setter() { + LogOutput listener = mock(LogOutput.class); + assertThat(new LoggingConfiguration(null).setLogOutput(listener).getLogOutput()).isEqualTo(listener); + } + + @Test + public void test_deprecated_log_properties() { + Map<String, String> properties = Maps.newHashMap(); + assertThat(new LoggingConfiguration(null).setProperties(properties) + .getSubstitutionVariable(LoggingConfiguration.PROPERTY_ROOT_LOGGER_LEVEL)).isEqualTo(LoggingConfiguration.LEVEL_ROOT_DEFAULT); + + properties.put("sonar.verbose", "true"); + LoggingConfiguration conf = new LoggingConfiguration(null).setProperties(properties); + assertThat(conf.getSubstitutionVariable(LoggingConfiguration.PROPERTY_ROOT_LOGGER_LEVEL)).isEqualTo(LoggingConfiguration.LEVEL_ROOT_VERBOSE); + assertThat(conf.getSubstitutionVariable(LoggingConfiguration.PROPERTY_SQL_LOGGER_LEVEL)).isEqualTo("WARN"); + + properties.put("sonar.verbose", "false"); + conf = new LoggingConfiguration(null).setProperties(properties); + assertThat(conf.getSubstitutionVariable(LoggingConfiguration.PROPERTY_ROOT_LOGGER_LEVEL)).isEqualTo(LoggingConfiguration.LEVEL_ROOT_DEFAULT); + assertThat(conf.getSubstitutionVariable(LoggingConfiguration.PROPERTY_SQL_LOGGER_LEVEL)).isEqualTo("WARN"); + + properties.put("sonar.verbose", "false"); + properties.put("sonar.log.profilingLevel", "FULL"); + conf = new LoggingConfiguration(null).setProperties(properties); + assertThat(conf.getSubstitutionVariable(LoggingConfiguration.PROPERTY_ROOT_LOGGER_LEVEL)).isEqualTo("DEBUG"); + assertThat(conf.getSubstitutionVariable(LoggingConfiguration.PROPERTY_SQL_LOGGER_LEVEL)).isEqualTo("TRACE"); + + properties.put("sonar.verbose", "false"); + properties.put("sonar.log.profilingLevel", "BASIC"); + conf = new LoggingConfiguration(null).setProperties(properties); + assertThat(conf.getSubstitutionVariable(LoggingConfiguration.PROPERTY_ROOT_LOGGER_LEVEL)).isEqualTo("DEBUG"); + assertThat(conf.getSubstitutionVariable(LoggingConfiguration.PROPERTY_SQL_LOGGER_LEVEL)).isEqualTo("WARN"); + } + + @Test + public void test_log_level_property() { + Map<String, String> properties = Maps.newHashMap(); + LoggingConfiguration conf = new LoggingConfiguration(null).setProperties(properties); + assertThat(conf.getSubstitutionVariable(LoggingConfiguration.PROPERTY_ROOT_LOGGER_LEVEL)).isEqualTo("INFO"); + assertThat(conf.getSubstitutionVariable(LoggingConfiguration.PROPERTY_SQL_LOGGER_LEVEL)).isEqualTo("WARN"); + + properties.put("sonar.log.level", "INFO"); + conf = new LoggingConfiguration(null).setProperties(properties); + assertThat(conf.getSubstitutionVariable(LoggingConfiguration.PROPERTY_ROOT_LOGGER_LEVEL)).isEqualTo("INFO"); + assertThat(conf.getSubstitutionVariable(LoggingConfiguration.PROPERTY_SQL_LOGGER_LEVEL)).isEqualTo("WARN"); + + properties.put("sonar.log.level", "DEBUG"); + conf = new LoggingConfiguration(null).setProperties(properties); + assertThat(conf.getSubstitutionVariable(LoggingConfiguration.PROPERTY_ROOT_LOGGER_LEVEL)).isEqualTo("DEBUG"); + assertThat(conf.getSubstitutionVariable(LoggingConfiguration.PROPERTY_SQL_LOGGER_LEVEL)).isEqualTo("WARN"); + + properties.put("sonar.log.level", "TRACE"); + conf = new LoggingConfiguration(null).setProperties(properties); + assertThat(conf.getSubstitutionVariable(LoggingConfiguration.PROPERTY_ROOT_LOGGER_LEVEL)).isEqualTo("DEBUG"); + assertThat(conf.getSubstitutionVariable(LoggingConfiguration.PROPERTY_SQL_LOGGER_LEVEL)).isEqualTo("TRACE"); + } + + @Test + public void testDefaultFormat() { + assertThat(new LoggingConfiguration(null) + .getSubstitutionVariable(LoggingConfiguration.PROPERTY_FORMAT)).isEqualTo(LoggingConfiguration.FORMAT_DEFAULT); + } + + @Test + public void testMavenFormat() { + assertThat(new LoggingConfiguration(new EnvironmentInformation("maven", "1.0")) + .getSubstitutionVariable(LoggingConfiguration.PROPERTY_FORMAT)).isEqualTo(LoggingConfiguration.FORMAT_MAVEN); + } + + @Test + public void testSetFormat() { + assertThat(new LoggingConfiguration(null).setFormat("%d %level") + .getSubstitutionVariable(LoggingConfiguration.PROPERTY_FORMAT)).isEqualTo("%d %level"); + } + + @Test + public void shouldNotSetBlankFormat() { + assertThat(new LoggingConfiguration(null).setFormat(null) + .getSubstitutionVariable(LoggingConfiguration.PROPERTY_FORMAT)).isEqualTo(LoggingConfiguration.FORMAT_DEFAULT); + + assertThat(new LoggingConfiguration(null).setFormat("") + .getSubstitutionVariable(LoggingConfiguration.PROPERTY_FORMAT)).isEqualTo(LoggingConfiguration.FORMAT_DEFAULT); + + assertThat(new LoggingConfiguration(null).setFormat(" ") + .getSubstitutionVariable(LoggingConfiguration.PROPERTY_FORMAT)).isEqualTo(LoggingConfiguration.FORMAT_DEFAULT); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrapper/LoggingConfiguratorTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrapper/LoggingConfiguratorTest.java new file mode 100644 index 00000000000..2d719e4a657 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrapper/LoggingConfiguratorTest.java @@ -0,0 +1,184 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrapper; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.assertj.core.api.Assertions.assertThat; + +public class LoggingConfiguratorTest { + private static final String DEFAULT_CLASSPATH_CONF = "/org/sonar/batch/bootstrapper/logback.xml"; + private static final String TEST_STR = "foo"; + private LoggingConfiguration conf = new LoggingConfiguration(); + private ByteArrayOutputStream out; + private SimpleLogListener listener; + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + @Before + public void setUp() { + out = new ByteArrayOutputStream(); + conf = new LoggingConfiguration(); + listener = new SimpleLogListener(); + } + + private class SimpleLogListener implements LogOutput { + String msg; + LogOutput.Level level; + + @Override + public void log(String msg, LogOutput.Level level) { + this.msg = msg; + this.level = level; + } + } + + @Test + public void testWithFile() throws FileNotFoundException, IOException { + InputStream is = this.getClass().getResourceAsStream(DEFAULT_CLASSPATH_CONF); + File tmpFolder = folder.getRoot(); + File testFile = new File(tmpFolder, "test"); + OutputStream os = new FileOutputStream(testFile); + IOUtils.copy(is, os); + os.close(); + + conf.setLogOutput(listener); + LoggingConfigurator.apply(conf, testFile); + + Logger logger = LoggerFactory.getLogger(this.getClass()); + logger.info(TEST_STR); + + assertThat(listener.msg).endsWith(TEST_STR); + assertThat(listener.level).isEqualTo(LogOutput.Level.INFO); + } + + @Test + public void testCustomAppender() throws UnsupportedEncodingException { + conf.setLogOutput(listener); + LoggingConfigurator.apply(conf); + + Logger logger = LoggerFactory.getLogger(this.getClass()); + logger.info(TEST_STR); + + assertThat(listener.msg).endsWith(TEST_STR); + assertThat(listener.level).isEqualTo(LogOutput.Level.INFO); + } + + @Test + public void testNoStdout() throws UnsupportedEncodingException { + System.setOut(new PrintStream(out, false, StandardCharsets.UTF_8.name())); + conf.setLogOutput(listener); + LoggingConfigurator.apply(conf); + + Logger logger = LoggerFactory.getLogger(this.getClass()); + + logger.error(TEST_STR); + logger.info(TEST_STR); + logger.debug(TEST_STR); + assertThat(out.size()).isEqualTo(0); + } + + @Test + public void testConfigureMultipleTimes() throws UnsupportedEncodingException { + System.setOut(new PrintStream(out, false, StandardCharsets.UTF_8.name())); + conf.setLogOutput(listener); + LoggingConfigurator.apply(conf); + + Logger logger = LoggerFactory.getLogger(this.getClass()); + logger.debug("debug"); + assertThat(listener.msg).isNull(); + + conf.setVerbose(true); + LoggingConfigurator.apply(conf); + + logger.debug("debug"); + assertThat(listener.msg).isEqualTo("debug"); + } + + @Test + public void testFormatNoEffect() throws UnsupportedEncodingException { + conf.setLogOutput(listener); + conf.setFormat("%t"); + + LoggingConfigurator.apply(conf); + Logger logger = LoggerFactory.getLogger(this.getClass()); + + logger.info("info"); + + assertThat(listener.msg).isEqualTo("info"); + } + + @Test + public void testSqlClasspath() throws UnsupportedEncodingException { + String classpath = "/org/sonar/batch/bootstrapper/logback.xml"; + + conf.setLogOutput(listener); + conf.setShowSql(true); + + LoggingConfigurator.apply(conf, classpath); + + Logger logger = LoggerFactory.getLogger("java.sql"); + logger.info("foo"); + + assertThat(listener.msg).endsWith(TEST_STR); + } + + @Test + public void testNoListener() throws UnsupportedEncodingException { + System.setOut(new PrintStream(out, false, StandardCharsets.UTF_8.name())); + LoggingConfigurator.apply(conf); + + Logger logger = LoggerFactory.getLogger(this.getClass()); + logger.info("info"); + + assertThat(new String(out.toByteArray(), StandardCharsets.UTF_8)).contains("info"); + } + + @Test + public void testNoSqlClasspath() throws UnsupportedEncodingException { + String classpath = "/org/sonar/batch/bootstrapper/logback.xml"; + + conf.setLogOutput(listener); + conf.setShowSql(false); + + LoggingConfigurator.apply(conf, classpath); + + Logger logger = LoggerFactory.getLogger("java.sql"); + logger.info("foo"); + + assertThat(listener.msg).isNull(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/cache/DefaultProjectCacheStatusTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/cache/DefaultProjectCacheStatusTest.java new file mode 100644 index 00000000000..c8f105de443 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/cache/DefaultProjectCacheStatusTest.java @@ -0,0 +1,91 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cache; + +import com.google.common.io.Files; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.home.cache.PersistentCache; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DefaultProjectCacheStatusTest { + @Rule + public TemporaryFolder tmp = new TemporaryFolder(); + + @Rule + public ExpectedException exception = ExpectedException.none(); + + ProjectCacheStatus cacheStatus; + PersistentCache cache = mock(PersistentCache.class); + + @Before + public void setUp() { + when(cache.getDirectory()).thenReturn(tmp.getRoot().toPath()); + cacheStatus = new DefaultProjectCacheStatus(cache); + } + + @Test + public void errorSave() throws IOException { + when(cache.getDirectory()).thenReturn(tmp.getRoot().toPath().resolve("unexistent_folder")); + cacheStatus = new DefaultProjectCacheStatus(cache); + + exception.expect(IllegalStateException.class); + exception.expectMessage("Failed to write cache sync status"); + cacheStatus.save(); + } + + @Test + public void errorStatus() throws IOException { + Files.write("trash".getBytes(StandardCharsets.UTF_8), new File(tmp.getRoot(), "cache-sync-status")); + cacheStatus = new DefaultProjectCacheStatus(cache); + + exception.expect(IllegalStateException.class); + exception.expectMessage("Failed to read cache sync status"); + cacheStatus.getSyncStatus(); + } + + @Test + public void testSave() { + cacheStatus.save(); + assertThat(cacheStatus.getSyncStatus()).isNotNull(); + assertThat(age(cacheStatus.getSyncStatus())).isLessThan(2000); + } + + @Test + public void testDelete() { + cacheStatus.save(); + cacheStatus.delete(); + assertThat(cacheStatus.getSyncStatus()).isNull(); + } + + private long age(Date date) { + return (new Date().getTime()) - date.getTime(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/cache/GlobalPersistentCacheProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/cache/GlobalPersistentCacheProviderTest.java new file mode 100644 index 00000000000..3f019caae02 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/cache/GlobalPersistentCacheProviderTest.java @@ -0,0 +1,83 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cache; + +import org.sonar.home.cache.PersistentCache; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; + +import static org.junit.Assert.*; +import static org.assertj.core.api.Assertions.assertThat; +import org.sonar.batch.bootstrap.GlobalProperties; +import org.junit.Before; +import org.junit.Test; +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; + +public class GlobalPersistentCacheProviderTest { + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private GlobalPersistentCacheProvider provider; + private GlobalProperties globalProperties; + + @Before + public void setUp() { + HashMap<String, String> map = new HashMap<>(); + map.put("sonar.userHome", temp.getRoot().getAbsolutePath()); + globalProperties = new GlobalProperties(map); + provider = new GlobalPersistentCacheProvider(); + } + + @Test + public void test_path() { + PersistentCache cache = provider.provide(globalProperties); + assertThat(cache.getDirectory()).isEqualTo(temp.getRoot().toPath() + .resolve("ws_cache") + .resolve("http%3A%2F%2Flocalhost%3A9000") + .resolve("global")); + } + + @Test + public void test_singleton() { + assertTrue(provider.provide(globalProperties) == provider.provide(globalProperties)); + } + + @Test + public void test_without_sonar_home() { + globalProperties = new GlobalProperties(new HashMap<String, String>()); + PersistentCache cache = provider.provide(globalProperties); + assertThat(cache.getDirectory().toAbsolutePath().toString()).startsWith(findHome().toAbsolutePath().toString()); + + } + + private static Path findHome() { + String home = System.getenv("SONAR_USER_HOME"); + + if (home != null) { + return Paths.get(home); + } + + home = System.getProperty("user.home"); + return Paths.get(home, ".sonar"); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/cache/NonAssociatedCacheSynchronizerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/cache/NonAssociatedCacheSynchronizerTest.java new file mode 100644 index 00000000000..c06bb94ca08 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/cache/NonAssociatedCacheSynchronizerTest.java @@ -0,0 +1,93 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cache; + +import com.google.common.collect.ImmutableList; +import java.util.Date; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.sonar.batch.repository.QualityProfileLoader; +import org.sonar.batch.rule.ActiveRulesLoader; +import org.sonar.batch.rule.LoadedActiveRule; +import org.sonar.batch.rule.RulesLoader; +import org.sonarqube.ws.QualityProfiles.SearchWsResponse.QualityProfile; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +public class NonAssociatedCacheSynchronizerTest { + private NonAssociatedCacheSynchronizer synchronizer; + + @Mock + private RulesLoader rulesLoader; + @Mock + private QualityProfileLoader qualityProfileLoader; + @Mock + private ActiveRulesLoader activeRulesLoader; + @Mock + private ProjectCacheStatus cacheStatus; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + QualityProfile pf = QualityProfile.newBuilder().setKey("profile").setName("profile").setLanguage("lang").build(); + LoadedActiveRule ar = new LoadedActiveRule(); + + when(qualityProfileLoader.loadDefault(null, null)).thenReturn(ImmutableList.of(pf)); + when(activeRulesLoader.load("profile", null)).thenReturn(ImmutableList.of(ar)); + + synchronizer = new NonAssociatedCacheSynchronizer(rulesLoader, qualityProfileLoader, activeRulesLoader, cacheStatus); + } + + @Test + public void dont_sync_if_exists() { + when(cacheStatus.getSyncStatus()).thenReturn(new Date()); + synchronizer.execute(false); + verifyZeroInteractions(rulesLoader, qualityProfileLoader, activeRulesLoader); + } + + @Test + public void always_sync_if_force() { + when(cacheStatus.getSyncStatus()).thenReturn(new Date()); + synchronizer.execute(true); + checkSync(); + } + + @Test + public void sync_if_doesnt_exist() { + synchronizer.execute(false); + checkSync(); + } + + private void checkSync() { + verify(cacheStatus).getSyncStatus(); + verify(cacheStatus).save(); + verify(rulesLoader).load(null); + verify(qualityProfileLoader).loadDefault(null, null); + verify(activeRulesLoader).load("profile", null); + + verifyNoMoreInteractions(qualityProfileLoader, activeRulesLoader); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/cache/ProjectCacheSynchronizerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/cache/ProjectCacheSynchronizerTest.java new file mode 100644 index 00000000000..52df42e035d --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/cache/ProjectCacheSynchronizerTest.java @@ -0,0 +1,197 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cache; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.util.Date; +import java.util.HashMap; +import org.apache.commons.lang.mutable.MutableBoolean; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.batch.analysis.AnalysisProperties; +import org.sonar.batch.analysis.DefaultAnalysisMode; +import org.sonar.batch.repository.DefaultProjectRepositoriesLoader; +import org.sonar.batch.repository.DefaultQualityProfileLoader; +import org.sonar.batch.repository.DefaultServerIssuesLoader; +import org.sonar.batch.repository.ProjectRepositories; +import org.sonar.batch.repository.ProjectRepositoriesLoader; +import org.sonar.batch.repository.QualityProfileLoader; +import org.sonar.batch.repository.ServerIssuesLoader; +import org.sonar.batch.repository.user.UserRepositoryLoader; +import org.sonar.batch.rule.ActiveRulesLoader; +import org.sonar.batch.rule.DefaultActiveRulesLoader; +import org.sonar.batch.rule.LoadedActiveRule; +import org.sonar.batch.rule.RulesLoader; +import org.sonarqube.ws.QualityProfiles.SearchWsResponse.QualityProfile; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +public class ProjectCacheSynchronizerTest { + private static final String PROJECT_KEY = "org.codehaus.sonar-plugins:sonar-scm-git-plugin"; + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Mock + private ProjectDefinition project; + @Mock + private ProjectCacheStatus cacheStatus; + @Mock + private DefaultAnalysisMode analysisMode; + @Mock + private AnalysisProperties properties; + @Mock + private RulesLoader rulesLoader; + + private ServerIssuesLoader issuesLoader; + private UserRepositoryLoader userRepositoryLoader; + private QualityProfileLoader qualityProfileLoader; + private ActiveRulesLoader activeRulesLoader; + private ProjectRepositoriesLoader projectRepositoriesLoader; + + @Before + public void setUp() throws IOException { + MockitoAnnotations.initMocks(this); + + when(analysisMode.isIssues()).thenReturn(true); + when(properties.properties()).thenReturn(new HashMap<String, String>()); + } + + private ProjectCacheSynchronizer createMockedLoaders(boolean projectExists, Date lastAnalysisDate) { + issuesLoader = mock(DefaultServerIssuesLoader.class); + userRepositoryLoader = mock(UserRepositoryLoader.class); + qualityProfileLoader = mock(DefaultQualityProfileLoader.class); + activeRulesLoader = mock(DefaultActiveRulesLoader.class); + projectRepositoriesLoader = mock(DefaultProjectRepositoriesLoader.class); + + QualityProfile pf = QualityProfile.newBuilder().setKey("profile").setName("profile").setLanguage("lang").build(); + LoadedActiveRule ar = new LoadedActiveRule(); + ProjectRepositories repo = mock(ProjectRepositories.class); + + when(qualityProfileLoader.load(PROJECT_KEY, null, null)).thenReturn(ImmutableList.of(pf)); + when(qualityProfileLoader.loadDefault(null, null)).thenReturn(ImmutableList.of(pf)); + when(activeRulesLoader.load("profile", null)).thenReturn(ImmutableList.of(ar)); + when(repo.lastAnalysisDate()).thenReturn(lastAnalysisDate); + when(repo.exists()).thenReturn(projectExists); + when(projectRepositoriesLoader.load(anyString(), anyBoolean(), any(MutableBoolean.class))).thenReturn(repo); + + return new ProjectCacheSynchronizer(rulesLoader, qualityProfileLoader, projectRepositoriesLoader, activeRulesLoader, issuesLoader, userRepositoryLoader, cacheStatus); + } + + @Test + public void testLoadersUsage() { + ProjectCacheSynchronizer synchronizer = createMockedLoaders(true, new Date()); + synchronizer.load(PROJECT_KEY, false); + + verify(issuesLoader).load(eq(PROJECT_KEY), any(Function.class)); + verify(rulesLoader).load(null); + verify(qualityProfileLoader).load(PROJECT_KEY, null, null); + verify(activeRulesLoader).load("profile", null); + verify(projectRepositoriesLoader).load(eq(PROJECT_KEY), eq(true), any(MutableBoolean.class)); + + verifyNoMoreInteractions(issuesLoader, userRepositoryLoader, qualityProfileLoader, activeRulesLoader, projectRepositoriesLoader); + } + + @Test + public void testLoadersUsage_NoLastAnalysis() { + ProjectCacheSynchronizer synchronizer = createMockedLoaders(true, null); + synchronizer.load(PROJECT_KEY, false); + + verify(projectRepositoriesLoader).load(eq(PROJECT_KEY), eq(true), any(MutableBoolean.class)); + verify(qualityProfileLoader).load(PROJECT_KEY, null, null); + verify(activeRulesLoader).load("profile", null); + + verifyNoMoreInteractions(issuesLoader, userRepositoryLoader, qualityProfileLoader, activeRulesLoader, projectRepositoriesLoader); + } + + @Test + public void testLoadersUsage_ProjectDoesntExist() { + ProjectCacheSynchronizer synchronizer = createMockedLoaders(false, null); + synchronizer.load(PROJECT_KEY, false); + + verify(projectRepositoriesLoader).load(eq(PROJECT_KEY), eq(true), any(MutableBoolean.class)); + verify(qualityProfileLoader).loadDefault(null, null); + verify(activeRulesLoader).load("profile", null); + + verifyNoMoreInteractions(issuesLoader, userRepositoryLoader, qualityProfileLoader, activeRulesLoader, projectRepositoriesLoader); + } + + @Test + public void testLastAnalysisToday() { + ProjectCacheSynchronizer synchronizer = createMockedLoaders(true, new Date()); + + when(cacheStatus.getSyncStatus()).thenReturn(new Date()); + synchronizer.load(PROJECT_KEY, false); + + verify(cacheStatus).getSyncStatus(); + verifyNoMoreInteractions(issuesLoader, userRepositoryLoader, qualityProfileLoader, activeRulesLoader, projectRepositoriesLoader, cacheStatus); + } + + @Test + public void testLastAnalysisYesterday() { + ProjectCacheSynchronizer synchronizer = createMockedLoaders(true, new Date()); + + Date d = new Date(new Date().getTime() - 60 * 60 * 24 * 1000); + when(cacheStatus.getSyncStatus()).thenReturn(d); + synchronizer.load(PROJECT_KEY, false); + + verify(cacheStatus).save(); + verify(cacheStatus).getSyncStatus(); + } + + @Test + public void testDontFailOnError() { + ProjectCacheSynchronizer synchronizer = createMockedLoaders(true, new Date()); + + Date d = new Date(new Date().getTime() - 60 * 60 * 24 * 1000); + when(cacheStatus.getSyncStatus()).thenReturn(d); + + when(projectRepositoriesLoader.load(anyString(), anyBoolean(), any(MutableBoolean.class))).thenThrow(IllegalStateException.class); + synchronizer.load(PROJECT_KEY, false); + + verify(cacheStatus).getSyncStatus(); + verifyNoMoreInteractions(cacheStatus); + } + + @Test + public void testForce() { + ProjectCacheSynchronizer synchronizer = createMockedLoaders(true, new Date()); + + when(cacheStatus.getSyncStatus()).thenReturn(new Date()); + synchronizer.load(PROJECT_KEY, true); + + verify(cacheStatus).save(); + verify(cacheStatus).getSyncStatus(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/cache/ProjectPersistentCacheProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/cache/ProjectPersistentCacheProviderTest.java new file mode 100644 index 00000000000..69c142556ae --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/cache/ProjectPersistentCacheProviderTest.java @@ -0,0 +1,80 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cache; + +import org.sonar.api.batch.bootstrap.ProjectKey; + +import org.sonar.batch.util.BatchUtils; +import org.sonar.batch.analysis.DefaultAnalysisMode; +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; +import org.sonar.batch.bootstrap.GlobalProperties; +import org.sonar.batch.cache.ProjectPersistentCacheProvider; + +import java.io.File; +import java.nio.file.Path; +import java.util.Collections; + +import static org.mockito.Mockito.mock; +import org.junit.Before; +import static org.assertj.core.api.Assertions.assertThat; +import org.junit.Test; + +public class ProjectPersistentCacheProviderTest { + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private ProjectPersistentCacheProvider provider = null; + private GlobalProperties props = null; + private DefaultAnalysisMode mode = null; + private ProjectKey key = null; + + @Before + public void prepare() { + key = new ProjectKeySupplier("proj"); + props = new GlobalProperties(Collections.<String, String>emptyMap()); + mode = mock(DefaultAnalysisMode.class); + provider = new ProjectPersistentCacheProvider(); + } + + @Test + public void test_singleton() { + assertThat(provider.provide(props, mode, key)).isEqualTo(provider.provide(props, mode, key)); + } + + @Test + public void test_cache_dir() { + assertThat(provider.provide(props, mode, key).getDirectory().toFile()).exists().isDirectory(); + } + + @Test + public void test_home() { + File f = temp.getRoot(); + props.properties().put("sonar.userHome", f.getAbsolutePath()); + Path expected = f.toPath() + .resolve("ws_cache") + .resolve("http%3A%2F%2Flocalhost%3A9000") + .resolve( BatchUtils.getServerVersion()) + .resolve("projects") + .resolve("proj"); + + assertThat(provider.provide(props, mode, key).getDirectory()).isEqualTo(expected); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/cache/ProjectSyncContainerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/cache/ProjectSyncContainerTest.java new file mode 100644 index 00000000000..948f888cd68 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/cache/ProjectSyncContainerTest.java @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cache; + +import java.util.HashMap; +import org.junit.Test; +import org.sonar.batch.bootstrap.GlobalProperties; +import org.sonar.core.platform.ComponentContainer; +import org.sonar.home.cache.PersistentCache; +import org.sonar.scanner.protocol.input.ProjectRepositories; +import org.sonarqube.ws.client.WsClient; + +import static org.mockito.Mockito.mock; + +public class ProjectSyncContainerTest { + private ComponentContainer createParentContainer() { + PersistentCache cache = mock(PersistentCache.class); + WsClient server = mock(WsClient.class); + + GlobalProperties globalProps = new GlobalProperties(new HashMap<String, String>()); + ComponentContainer parent = new ComponentContainer(); + parent.add(cache); + parent.add(server); + parent.add(globalProps); + return parent; + } + + @Test + public void testProjectRepository() { + ProjectSyncContainer container = new ProjectSyncContainer(createParentContainer(), "my:project", true); + container.doBeforeStart(); + container.getPicoContainer().start(); + container.getComponentByType(ProjectRepositories.class); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/cache/StrategyWSLoaderProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/cache/StrategyWSLoaderProviderTest.java new file mode 100644 index 00000000000..ce9d88a037c --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/cache/StrategyWSLoaderProviderTest.java @@ -0,0 +1,59 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cache; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.sonar.batch.bootstrap.BatchWsClient; +import org.sonar.batch.cache.WSLoader.LoadStrategy; +import org.sonar.home.cache.PersistentCache; + +import static org.assertj.core.api.Assertions.assertThat; + +public class StrategyWSLoaderProviderTest { + @Mock + private PersistentCache cache; + + @Mock + private BatchWsClient client; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testStrategy() { + StrategyWSLoaderProvider provider = new StrategyWSLoaderProvider(LoadStrategy.CACHE_FIRST); + WSLoader wsLoader = provider.provide(cache, client); + + assertThat(wsLoader.getDefaultStrategy()).isEqualTo(LoadStrategy.CACHE_FIRST); + } + + @Test + public void testSingleton() { + StrategyWSLoaderProvider provider = new StrategyWSLoaderProvider(LoadStrategy.CACHE_FIRST); + WSLoader wsLoader = provider.provide(cache, client); + + assertThat(provider.provide(null, null)).isEqualTo(wsLoader); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/cache/WSLoaderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/cache/WSLoaderTest.java new file mode 100644 index 00000000000..ad7bb763d67 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/cache/WSLoaderTest.java @@ -0,0 +1,264 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cache; + +import java.io.IOException; +import java.io.InputStream; +import org.apache.commons.io.IOUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.InOrder; +import org.mockito.Mockito; +import org.sonar.batch.bootstrap.BatchWsClient; +import org.sonar.batch.cache.WSLoader.LoadStrategy; +import org.sonar.home.cache.PersistentCache; +import org.sonarqube.ws.client.HttpException; +import org.sonarqube.ws.client.MockWsResponse; +import org.sonarqube.ws.client.WsRequest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.inOrder; +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.when; + +public class WSLoaderTest { + private final static String ID = "dummy"; + private final static String cacheValue = "cache"; + private final static String serverValue = "server"; + + @Rule + public ExpectedException exception = ExpectedException.none(); + + BatchWsClient ws = mock(BatchWsClient.class, Mockito.RETURNS_DEEP_STUBS); + PersistentCache cache = mock(PersistentCache.class); + + @Test + public void dont_retry_server_offline() throws IOException { + turnServerOffline(); + when(cache.getString(ID)).thenReturn(cacheValue); + WSLoader underTest = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws); + + assertResult(underTest.loadString(ID), cacheValue, true); + assertResult(underTest.loadString(ID), cacheValue, true); + + assertUsedServer(1); + assertUsedCache(2); + } + + @Test + public void get_stream_from_cache() throws IOException { + InputStream is = IOUtils.toInputStream("is"); + when(cache.getStream(ID)).thenReturn(is); + + WSLoader loader = new WSLoader(LoadStrategy.CACHE_FIRST, cache, ws); + WSLoaderResult<InputStream> result = loader.loadStream(ID); + + assertThat(result.get()).isEqualTo(is); + verify(cache).getStream(ID); + verifyNoMoreInteractions(cache, ws); + } + + @Test + public void put_stream_in_cache() throws IOException { + InputStream input = IOUtils.toInputStream("is"); + + when(ws.call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(input)); + when(cache.getStream(ID)).thenReturn(input); + + // SERVER_FIRST -> load from server then put to cache + WSLoader underTest = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws); + WSLoaderResult<InputStream> result = underTest.loadStream(ID); + assertThat(result.get()).isEqualTo(input); + + InOrder inOrder = inOrder(ws, cache); + inOrder.verify(ws).call(any(WsRequest.class)); + inOrder.verify(cache).put(eq(ID), any(InputStream.class)); + inOrder.verify(cache).getStream(ID); + verifyNoMoreInteractions(cache, ws); + } + + @Test + public void test_cache_strategy_fallback() throws IOException { + turnCacheEmpty(); + when(ws.call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(serverValue)); + WSLoader loader = new WSLoader(LoadStrategy.CACHE_FIRST, cache, ws); + + assertResult(loader.loadString(ID), serverValue, false); + + InOrder inOrder = inOrder(ws, cache); + inOrder.verify(cache).getString(ID); + inOrder.verify(ws).call(any(WsRequest.class)); + } + + @Test + public void test_server_strategy_fallback() throws IOException { + turnServerOffline(); + when(cache.getString(ID)).thenReturn(cacheValue); + WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws); + + assertResult(loader.loadString(ID), cacheValue, true); + + InOrder inOrder = inOrder(ws, cache); + inOrder.verify(ws).call(any(WsRequest.class)); + inOrder.verify(cache).getString(ID); + } + + @Test + public void test_put_cache() throws IOException { + when(ws.call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(serverValue)); + WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws); + loader.loadString(ID); + verify(cache).put(ID, serverValue.getBytes()); + } + + @Test + public void test_throw_cache_exception_fallback() throws IOException { + turnServerOffline(); + + when(cache.getString(ID)).thenThrow(new NullPointerException()); + WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws); + + try { + loader.loadString(ID); + fail("NPE expected"); + } catch (NullPointerException e) { + assertUsedServer(1); + assertUsedCache(1); + } + } + + @Test + public void test_throw_cache_exception() throws IOException { + when(cache.getString(ID)).thenThrow(new IllegalStateException()); + + WSLoader loader = new WSLoader(LoadStrategy.CACHE_FIRST, cache, ws); + + try { + loader.loadString(ID); + fail("IllegalStateException expected"); + } catch (IllegalStateException e) { + assertUsedServer(0); + assertUsedCache(1); + } + } + + @Test + public void test_throw_http_exceptions() { + when(ws.call(any(WsRequest.class))).thenThrow(new HttpException("url", 500)); + + WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws); + + try { + loader.loadString(ID); + fail("IllegalStateException expected"); + } catch (HttpException e) { + // cache should not be used + verifyNoMoreInteractions(cache); + } + } + + @Test + public void test_server_only_not_available() { + turnServerOffline(); + + exception.expect(IllegalStateException.class); + exception.expectMessage("Server is not available"); + + WSLoader loader = new WSLoader(LoadStrategy.SERVER_ONLY, cache, ws); + loader.loadString(ID); + } + + @Test + public void test_server_cache_not_available() throws IOException { + turnServerOffline(); + turnCacheEmpty(); + + exception.expect(IllegalStateException.class); + exception.expectMessage("Server is not accessible and data is not cached"); + + WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws); + loader.loadString(ID); + } + + @Test + public void test_cache_only_available() throws IOException { + turnCacheEmpty(); + + exception.expect(IllegalStateException.class); + exception.expectMessage("Data is not cached"); + + WSLoader loader = new WSLoader(LoadStrategy.CACHE_ONLY, cache, ws); + loader.loadString(ID); + } + + @Test + public void test_server_strategy() throws IOException { + when(ws.call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(serverValue)); + WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws); + assertResult(loader.loadString(ID), serverValue, false); + + // should not fetch from cache + verify(cache).put(ID, serverValue.getBytes()); + verifyNoMoreInteractions(cache); + } + + @Test(expected = IllegalStateException.class) + public void test_server_only() throws IOException { + turnServerOffline(); + WSLoader loader = new WSLoader(LoadStrategy.SERVER_ONLY, cache, ws); + loader.loadString(ID); + } + + @Test + public void test_string() { + when(ws.call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(serverValue)); + WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws); + assertResult(loader.loadString(ID), serverValue, false); + } + + private void assertUsedCache(int times) throws IOException { + verify(cache, times(times)).getString(ID); + } + + private void assertUsedServer(int times) { + verify(ws, times(times)).call(any(WsRequest.class)); + } + + private void assertResult(WSLoaderResult<String> result, String expected, boolean fromCache) { + assertThat(result).isNotNull(); + assertThat(result.get()).isEqualTo(expected); + assertThat(result.isFromCache()).isEqualTo(fromCache); + } + + private void turnServerOffline() { + when(ws.call(any(WsRequest.class))).thenThrow(new IllegalStateException()); + } + + private void turnCacheEmpty() throws IOException { + when(cache.getString(ID)).thenReturn(null); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/CpdComponentsTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/CpdComponentsTest.java new file mode 100644 index 00000000000..814e7d69d38 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/CpdComponentsTest.java @@ -0,0 +1,32 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cpd; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CpdComponentsTest { + + @Test + public void getExtensions() { + assertThat(CpdComponents.all().size()).isGreaterThan(0); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/CpdExecutorTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/CpdExecutorTest.java new file mode 100644 index 00000000000..6a6c209348f --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/CpdExecutorTest.java @@ -0,0 +1,239 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cpd; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.fs.internal.DefaultInputModule; +import org.sonar.api.config.Settings; +import org.sonar.api.resources.Project; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; +import org.sonar.batch.cpd.index.SonarCpdBlockIndex; +import org.sonar.batch.index.BatchComponent; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.report.ReportPublisher; +import org.sonar.core.util.CloseableIterator; +import org.sonar.duplications.index.CloneGroup; +import org.sonar.duplications.index.ClonePart; +import org.sonar.scanner.protocol.output.ScannerReportReader; +import org.sonar.scanner.protocol.output.ScannerReportWriter; +import org.sonar.scanner.protocol.output.ScannerReport.Duplicate; +import org.sonar.scanner.protocol.output.ScannerReport.Duplication; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class CpdExecutorTest { + private CpdExecutor executor; + private Settings settings; + private SonarCpdBlockIndex index; + private ReportPublisher publisher; + private BatchComponentCache componentCache; + + @Rule + public LogTester logTester = new LogTester(); + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + // private AbstractCpdEngine engine; + + private ScannerReportReader reader; + private BatchComponent batchComponent1; + private BatchComponent batchComponent2; + private BatchComponent batchComponent3; + + @Before + public void setUp() throws IOException { + File outputDir = temp.newFolder(); + + settings = new Settings(); + index = mock(SonarCpdBlockIndex.class); + publisher = mock(ReportPublisher.class); + when(publisher.getWriter()).thenReturn(new ScannerReportWriter(outputDir)); + componentCache = new BatchComponentCache(); + executor = new CpdExecutor(settings, index, publisher, componentCache); + reader = new ScannerReportReader(outputDir); + + Project p = new Project("foo"); + componentCache.add(p, null).setInputComponent(new DefaultInputModule("foo")); + + batchComponent1 = createComponent("src/Foo.php", 5); + batchComponent2 = createComponent("src/Foo2.php", 5); + batchComponent3 = createComponent("src/Foo3.php", 5); + } + + private BatchComponent createComponent(String relativePath, int lines) { + org.sonar.api.resources.Resource sampleFile = org.sonar.api.resources.File.create("relativePath").setEffectiveKey("foo:" + relativePath); + return componentCache.add(sampleFile, null).setInputComponent(new DefaultInputFile("foo", relativePath).setLines(lines)); + } + + @Test + public void defaultMinimumTokens() { + assertThat(executor.getMinimumTokens("java")).isEqualTo(100); + } + + @Test + public void minimumTokensByLanguage() { + settings.setProperty("sonar.cpd.java.minimumTokens", "42"); + settings.setProperty("sonar.cpd.php.minimumTokens", "33"); + assertThat(executor.getMinimumTokens("java")).isEqualTo(42); + + settings.setProperty("sonar.cpd.java.minimumTokens", "42"); + settings.setProperty("sonar.cpd.php.minimumTokens", "33"); + assertThat(executor.getMinimumTokens("php")).isEqualTo(33); + } + + @Test + public void testNothingToSave() { + executor.saveDuplications(batchComponent1, Collections.<CloneGroup>emptyList()); + assertThat(reader.readComponentDuplications(batchComponent1.batchId())).hasSize(0); + } + + @Test + public void reportOneSimpleDuplicationBetweenTwoFiles() { + List<CloneGroup> groups = Arrays.asList(newCloneGroup(new ClonePart(batchComponent1.key(), 0, 2, 4), new ClonePart(batchComponent2.key(), 0, 15, 17))); + + executor.saveDuplications(batchComponent1, groups); + + Duplication[] dups = readDuplications(1); + assertDuplication(dups[0], 2, 4, batchComponent2.batchId(), 15, 17); + } + + @Test + public void reportDuplicationOnSameFile() throws Exception { + List<CloneGroup> groups = Arrays.asList(newCloneGroup(new ClonePart(batchComponent1.key(), 0, 5, 204), new ClonePart(batchComponent1.key(), 0, 215, 414))); + executor.saveDuplications(batchComponent1, groups); + + Duplication[] dups = readDuplications(1); + assertDuplication(dups[0], 5, 204, null, 215, 414); + } + + @Test + public void reportTooManyDuplicates() throws Exception { + // 1 origin part + 101 duplicates = 102 + List<ClonePart> parts = new ArrayList<>(CpdExecutor.MAX_CLONE_PART_PER_GROUP + 2); + for (int i = 0; i < CpdExecutor.MAX_CLONE_PART_PER_GROUP + 2; i++) { + parts.add(new ClonePart(batchComponent1.key(), i, i, i + 1)); + } + List<CloneGroup> groups = Arrays.asList(CloneGroup.builder().setLength(0).setOrigin(parts.get(0)).setParts(parts).build()); + executor.saveDuplications(batchComponent1, groups); + + Duplication[] dups = readDuplications(1); + assertThat(dups[0].getDuplicateList()).hasSize(CpdExecutor.MAX_CLONE_PART_PER_GROUP); + + assertThat(logTester.logs(LoggerLevel.WARN)) + .contains("Too many duplication references on file " + batchComponent1.inputComponent() + " for block at line 0. Keep only the first " + + CpdExecutor.MAX_CLONE_PART_PER_GROUP + " references."); + } + + @Test + public void reportTooManyDuplications() throws Exception { + // 1 origin part + 101 duplicates = 102 + List<CloneGroup> dups = new ArrayList<>(CpdExecutor.MAX_CLONE_GROUP_PER_FILE + 1); + for (int i = 0; i < CpdExecutor.MAX_CLONE_GROUP_PER_FILE + 1; i++) { + ClonePart clonePart = new ClonePart(batchComponent1.key(), i, i, i + 1); + ClonePart dupPart = new ClonePart(batchComponent1.key(), i + 1, i + 1, i + 2); + dups.add(newCloneGroup(clonePart, dupPart)); + } + executor.saveDuplications(batchComponent1, dups); + + assertThat(reader.readComponentDuplications(batchComponent1.batchId())).hasSize(CpdExecutor.MAX_CLONE_GROUP_PER_FILE); + + assertThat(logTester.logs(LoggerLevel.WARN)) + .contains("Too many duplication groups on file " + batchComponent1.inputComponent() + ". Keep only the first " + CpdExecutor.MAX_CLONE_GROUP_PER_FILE + " groups."); + } + + @Test + public void reportOneDuplicatedGroupInvolvingMoreThanTwoFiles() throws Exception { + List<CloneGroup> groups = Arrays + .asList(newCloneGroup(new ClonePart(batchComponent1.key(), 0, 5, 204), new ClonePart(batchComponent2.key(), 0, 15, 214), new ClonePart(batchComponent3.key(), 0, 25, 224))); + executor.saveDuplications(batchComponent1, groups); + + Duplication[] dups = readDuplications(1); + assertDuplication(dups[0], 5, 204, 2); + assertDuplicate(dups[0].getDuplicate(0), batchComponent2.batchId(), 15, 214); + assertDuplicate(dups[0].getDuplicate(1), batchComponent3.batchId(), 25, 224); + } + + @Test + public void reportTwoDuplicatedGroupsInvolvingThreeFiles() throws Exception { + List<CloneGroup> groups = Arrays.asList( + newCloneGroup(new ClonePart(batchComponent1.key(), 0, 5, 204), new ClonePart(batchComponent2.key(), 0, 15, 214)), + newCloneGroup(new ClonePart(batchComponent1.key(), 0, 15, 214), new ClonePart(batchComponent3.key(), 0, 15, 214))); + executor.saveDuplications(batchComponent1, groups); + + Duplication[] dups = readDuplications(2); + assertDuplication(dups[0], 5, 204, batchComponent2.batchId(), 15, 214); + assertDuplication(dups[1], 15, 214, batchComponent3.batchId(), 15, 214); + } + + private Duplication[] readDuplications(int expected) { + assertThat(reader.readComponentDuplications(batchComponent1.batchId())).hasSize(expected); + Duplication[] duplications = new Duplication[expected]; + CloseableIterator<Duplication> dups = reader.readComponentDuplications(batchComponent1.batchId()); + + for(int i = 0; i< expected; i++) { + duplications[i] = dups.next(); + } + dups.close(); + return duplications; + } + + private void assertDuplicate(Duplicate d, int otherFileRef, int rangeStartLine, int rangeEndLine) { + assertThat(d.getOtherFileRef()).isEqualTo(otherFileRef); + assertThat(d.getRange().getStartLine()).isEqualTo(rangeStartLine); + assertThat(d.getRange().getEndLine()).isEqualTo(rangeEndLine); + } + + private void assertDuplication(Duplication d, int originStartLine, int originEndLine, int numDuplicates) { + assertThat(d.getOriginPosition().getStartLine()).isEqualTo(originStartLine); + assertThat(d.getOriginPosition().getEndLine()).isEqualTo(originEndLine); + assertThat(d.getDuplicateList()).hasSize(numDuplicates); + } + + private void assertDuplication(Duplication d, int originStartLine, int originEndLine, Integer otherFileRef, int rangeStartLine, int rangeEndLine) { + assertThat(d.getOriginPosition().getStartLine()).isEqualTo(originStartLine); + assertThat(d.getOriginPosition().getEndLine()).isEqualTo(originEndLine); + assertThat(d.getDuplicateList()).hasSize(1); + if(otherFileRef != null) { + assertThat(d.getDuplicate(0).getOtherFileRef()).isEqualTo(otherFileRef); + } else { + assertThat(d.getDuplicate(0).hasOtherFileRef()).isFalse(); + } + assertThat(d.getDuplicate(0).getRange().getStartLine()).isEqualTo(rangeStartLine); + assertThat(d.getDuplicate(0).getRange().getEndLine()).isEqualTo(rangeEndLine); + } + + private CloneGroup newCloneGroup(ClonePart... parts) { + return CloneGroup.builder().setLength(0).setOrigin(parts[0]).setParts(Arrays.asList(parts)).build(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/CpdSensorTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/CpdSensorTest.java new file mode 100644 index 00000000000..ce27f0776c4 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/CpdSensorTest.java @@ -0,0 +1,80 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cpd; + +import java.io.IOException; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.internal.DefaultFileSystem; +import org.sonar.api.config.PropertyDefinitions; +import org.sonar.api.config.Settings; +import org.sonar.api.resources.Java; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CpdSensorTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + JavaCpdBlockIndexer sonarEngine; + DefaultCpdBlockIndexer sonarBridgeEngine; + CpdSensor sensor; + Settings settings; + + @Before + public void setUp() throws IOException { + sonarEngine = new JavaCpdBlockIndexer(null, null, null); + sonarBridgeEngine = new DefaultCpdBlockIndexer(new CpdMappings(), null, null, null); + settings = new Settings(new PropertyDefinitions(CpdComponents.class)); + + DefaultFileSystem fs = new DefaultFileSystem(temp.newFolder().toPath()); + sensor = new CpdSensor(sonarEngine, sonarBridgeEngine, settings, fs); + } + + @Test + public void test_global_skip() { + settings.setProperty("sonar.cpd.skip", true); + assertThat(sensor.isSkipped(Java.KEY)).isTrue(); + } + + @Test + public void should_not_skip_by_default() { + assertThat(sensor.isSkipped(Java.KEY)).isFalse(); + } + + @Test + public void should_skip_by_language() { + settings.setProperty("sonar.cpd.skip", false); + settings.setProperty("sonar.cpd.php.skip", true); + + assertThat(sensor.isSkipped("php")).isTrue(); + assertThat(sensor.isSkipped(Java.KEY)).isFalse(); + } + + @Test + public void test_engine() { + assertThat(sensor.getEngine(Java.KEY)).isSameAs(sonarEngine); + assertThat(sensor.getEngine("PHP")).isSameAs(sonarBridgeEngine); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/DefaultCpdBlockIndexerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/DefaultCpdBlockIndexerTest.java new file mode 100644 index 00000000000..9fcd03ac940 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/DefaultCpdBlockIndexerTest.java @@ -0,0 +1,80 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cpd; + +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.sonar.api.config.Settings; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class DefaultCpdBlockIndexerTest { + + private DefaultCpdBlockIndexer engine; + private Settings settings; + + @Before + public void init() { + settings = new Settings(); + engine = new DefaultCpdBlockIndexer(null, null, settings, null); + } + + @Test + public void shouldLogExclusions() { + Logger logger = mock(Logger.class); + engine.logExclusions(new String[0], logger); + verify(logger, never()).info(anyString()); + + logger = mock(Logger.class); + engine.logExclusions(new String[] {"Foo*", "**/Bar*"}, logger); + + String message = "Copy-paste detection exclusions:" + + "\n Foo*" + + "\n **/Bar*"; + verify(logger, times(1)).info(message); + } + + @Test + public void shouldReturnDefaultBlockSize() { + assertThat(DefaultCpdBlockIndexer.getDefaultBlockSize("cobol")).isEqualTo(30); + assertThat(DefaultCpdBlockIndexer.getDefaultBlockSize("abap")).isEqualTo(20); + assertThat(DefaultCpdBlockIndexer.getDefaultBlockSize("other")).isEqualTo(10); + } + + @Test + public void defaultBlockSize() { + + assertThat(engine.getBlockSize("java")).isEqualTo(10); + } + + @Test + public void blockSizeForCobol() { + settings.setProperty("sonar.cpd.cobol.minimumLines", "42"); + + assertThat(engine.getBlockSize("cobol")).isEqualTo(42); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/DuplicationPredicatesTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/DuplicationPredicatesTest.java new file mode 100644 index 00000000000..6b6fa4fa0e8 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/DuplicationPredicatesTest.java @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cpd; + +import com.google.common.base.Predicate; +import org.junit.Test; +import org.sonar.duplications.index.CloneGroup; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DuplicationPredicatesTest { + + @Test + public void testNumberOfUnitsNotLessThan() { + Predicate<CloneGroup> predicate = DuplicationPredicates.numberOfUnitsNotLessThan(5); + assertThat(predicate.apply(CloneGroup.builder().setLengthInUnits(6).build())).isTrue(); + assertThat(predicate.apply(CloneGroup.builder().setLengthInUnits(5).build())).isTrue(); + assertThat(predicate.apply(CloneGroup.builder().setLengthInUnits(4).build())).isFalse(); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/JavaCpdBlockIndexerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/JavaCpdBlockIndexerTest.java new file mode 100644 index 00000000000..851fb523815 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/JavaCpdBlockIndexerTest.java @@ -0,0 +1,106 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.cpd; + +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.internal.DefaultFileSystem; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.config.Settings; +import org.sonar.batch.cpd.index.SonarCpdBlockIndex; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.duplications.block.Block; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +public class JavaCpdBlockIndexerTest { + private static final String JAVA = "java"; + + @Mock + private SonarCpdBlockIndex index; + + @Captor + private ArgumentCaptor<List<Block>> blockCaptor; + + private Settings settings; + private JavaCpdBlockIndexer engine; + private DefaultInputFile file; + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Before + public void setUp() throws IOException { + MockitoAnnotations.initMocks(this); + + File baseDir = temp.newFolder(); + DefaultFileSystem fs = new DefaultFileSystem(baseDir); + file = new DefaultInputFile("foo", "src/ManyStatements.java").setLanguage(JAVA); + fs.add(file); + BatchComponentCache batchComponentCache = new BatchComponentCache(); + batchComponentCache.add(org.sonar.api.resources.File.create("src/Foo.java").setEffectiveKey("foo:src/ManyStatements.java"), null).setInputComponent(file); + File ioFile = file.file(); + FileUtils.copyURLToFile(this.getClass().getResource("ManyStatements.java"), ioFile); + + settings = new Settings(); + engine = new JavaCpdBlockIndexer(fs, settings, index); + } + + @Test + public void languageSupported() { + JavaCpdBlockIndexer engine = new JavaCpdBlockIndexer(mock(FileSystem.class), new Settings(), index); + assertThat(engine.isLanguageSupported(JAVA)).isTrue(); + assertThat(engine.isLanguageSupported("php")).isFalse(); + } + + @Test + public void testExclusions() { + settings.setProperty(CoreProperties.CPD_EXCLUSIONS, "**"); + engine.index(JAVA); + verifyZeroInteractions(index); + } + + @Test + public void testJavaIndexing() throws Exception { + engine.index(JAVA); + + verify(index).insert(eq(file), blockCaptor.capture()); + List<Block> blockList = blockCaptor.getValue(); + + assertThat(blockList).hasSize(26); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/deprecated/perspectives/PerspectiveBuilderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/deprecated/perspectives/PerspectiveBuilderTest.java new file mode 100644 index 00000000000..b07671362ce --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/deprecated/perspectives/PerspectiveBuilderTest.java @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.deprecated.perspectives; + +import org.junit.Test; +import org.sonar.api.component.Perspective; +import org.sonar.batch.index.BatchComponent; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PerspectiveBuilderTest { + @Test + public void testGetPerspectiveClass() throws Exception { + PerspectiveBuilder<FakePerspective> builder = new PerspectiveBuilder<FakePerspective>(FakePerspective.class) { + @Override + public FakePerspective loadPerspective(Class<FakePerspective> perspectiveClass, BatchComponent component) { + return null; + } + }; + + assertThat(builder.getPerspectiveClass()).isEqualTo(FakePerspective.class); + } + + static interface FakePerspective extends Perspective { + + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/events/BatchStepEventTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/events/BatchStepEventTest.java new file mode 100644 index 00000000000..3f3738b3900 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/events/BatchStepEventTest.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.events; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class BatchStepEventTest { + + private BatchStepEvent batchStepEvent = new BatchStepEvent("foo", true); + + @Test + public void testGetType() { + assertThat(batchStepEvent.getType()).isEqualTo(BatchStepHandler.class); + } + + @Test + public void testDispatch() { + BatchStepHandler handler = mock(BatchStepHandler.class); + batchStepEvent.dispatch(handler); + + verify(handler).onBatchStep(batchStepEvent); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/events/EventBusTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/events/EventBusTest.java new file mode 100644 index 00000000000..78fcd4926c2 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/events/EventBusTest.java @@ -0,0 +1,77 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.events; + +import org.junit.Test; +import org.sonar.api.batch.events.EventHandler; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class EventBusTest { + + @Test + public void shouldNotifyAboutEvent() { + FirstHandler firstHandler = mock(FirstHandler.class); + SecondHandler secondHandler = mock(SecondHandler.class); + EventBus eventBus = new EventBus(new EventHandler[] { firstHandler, secondHandler }); + + FirstEvent firstEvent = new FirstEvent(); + eventBus.fireEvent(firstEvent); + SecondEvent secondEvent = new SecondEvent(); + eventBus.fireEvent(secondEvent); + + verify(firstHandler).onEvent(firstEvent); + verify(secondHandler).onEvent(secondEvent); + } + + interface FirstHandler extends EventHandler { + void onEvent(FirstEvent event); + } + + static class FirstEvent extends BatchEvent<FirstHandler> { + @Override + protected void dispatch(FirstHandler handler) { + handler.onEvent(this); + } + + @Override + public Class getType() { + return FirstHandler.class; + } + } + + interface SecondHandler extends EventHandler { + void onEvent(SecondEvent event); + } + + static class SecondEvent extends BatchEvent<SecondHandler> { + @Override + protected void dispatch(SecondHandler handler) { + handler.onEvent(this); + } + + @Override + public Class getType() { + return SecondHandler.class; + } + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/index/AbstractCachesTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/index/AbstractCachesTest.java new file mode 100644 index 00000000000..b3e6592d9d2 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/index/AbstractCachesTest.java @@ -0,0 +1,77 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.index; + +import org.junit.After; + +import org.junit.Before; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import com.google.common.collect.ImmutableMap; +import org.sonar.api.CoreProperties; +import org.sonar.batch.bootstrap.GlobalProperties; +import org.sonar.batch.bootstrap.GlobalTempFolderProvider; + +import java.util.Map; + +import org.junit.ClassRule; +import org.junit.rules.TemporaryFolder; + +public abstract class AbstractCachesTest { + @ClassRule + public static TemporaryFolder temp = new TemporaryFolder(); + + protected static CachesManager cachesManager; + protected Caches caches; + + private static CachesManager createCacheOnTemp() { + Map<String, String> props = ImmutableMap.of(CoreProperties.WORKING_DIRECTORY, temp.getRoot().getAbsolutePath(), + CoreProperties.GLOBAL_WORKING_DIRECTORY, temp.getRoot().getAbsolutePath()); + + return new CachesManager(new GlobalTempFolderProvider().provide(new GlobalProperties(props))); + } + + @BeforeClass + public static void startClass() { + cachesManager = createCacheOnTemp(); + cachesManager.start(); + } + + @Before + public void start() { + caches = new Caches(cachesManager); + caches.start(); + } + + @After + public void stop() { + if (caches != null) { + caches.stop(); + caches = null; + } + } + + @AfterClass + public static void stopClass() { + if (cachesManager != null) { + cachesManager.stop(); + } + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/index/BatchComponentCacheTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/index/BatchComponentCacheTest.java new file mode 100644 index 00000000000..8d014b67321 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/index/BatchComponentCacheTest.java @@ -0,0 +1,53 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.index; + +import org.junit.Test; +import org.sonar.api.resources.File; +import org.sonar.api.resources.Resource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +public class BatchComponentCacheTest { + @Test + public void should_cache_resource() { + BatchComponentCache cache = new BatchComponentCache(); + String componentKey = "struts:src/org/struts/Action.java"; + Resource resource = File.create("org/struts/Action.java").setEffectiveKey(componentKey); + cache.add(resource, null); + + assertThat(cache.get(componentKey).resource()).isSameAs(resource); + assertThat(cache.get("other")).isNull(); + } + + @Test + public void should_fail_if_missing_component_key() { + BatchComponentCache cache = new BatchComponentCache(); + Resource resource = File.create("org/struts/Action.java").setEffectiveKey(null); + try { + cache.add(resource, null); + fail(); + } catch (IllegalStateException e) { + // success + assertThat(e).hasMessage("Missing resource effective key"); + } + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/index/BucketTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/index/BucketTest.java new file mode 100644 index 00000000000..a6a5f900088 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/index/BucketTest.java @@ -0,0 +1,58 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.index; + +import org.junit.Test; +import org.sonar.api.measures.Metric; +import org.sonar.api.resources.Directory; +import org.sonar.api.resources.File; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +public class BucketTest { + + Directory directory = Directory.create("org/foo"); + File javaFile = File.create("org/foo/Bar.java"); + Metric ncloc = new Metric("ncloc"); + + @Test + public void shouldManageRelationships() { + Bucket packageBucket = new Bucket(directory); + Bucket fileBucket = new Bucket(javaFile); + fileBucket.setParent(packageBucket); + + assertThat(fileBucket.getParent()).isEqualTo(packageBucket); + assertThat(packageBucket.getChildren()).containsExactly(fileBucket); + } + + @Test + public void shouldBeEquals() { + assertEquals(new Bucket(directory), new Bucket(directory)); + assertEquals(new Bucket(directory).hashCode(), new Bucket(directory).hashCode()); + } + + @Test + public void shouldNotBeEquals() { + assertFalse(new Bucket(directory).equals(new Bucket(javaFile))); + assertThat(new Bucket(directory).hashCode()).isNotEqualTo(new Bucket(javaFile).hashCode()); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/index/CacheTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/index/CacheTest.java new file mode 100644 index 00000000000..95ddcb8b766 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/index/CacheTest.java @@ -0,0 +1,250 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.index; + +import com.google.common.collect.Iterables; +import org.junit.Test; +import org.sonar.batch.index.Cache.Entry; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CacheTest extends AbstractCachesTest { + + @Test + public void one_part_key() { + Cache<String> cache = caches.createCache("capitals"); + + assertThat(cache.get("france")).isNull(); + + cache.put("france", "paris"); + cache.put("italy", "rome"); + assertThat(cache.get("france")).isEqualTo("paris"); + assertThat(cache.keySet()).containsOnly("france", "italy"); + assertThat(cache.keySet("france")).isEmpty(); + Iterable<String> values = cache.values(); + assertThat(values).containsOnly("paris", "rome"); + assertThat(values).containsOnly("paris", "rome"); + assertThat(cache.containsKey("france")).isTrue(); + + Iterable<Entry<String>> iterable = cache.entries(); + Cache.Entry[] entries = Iterables.toArray(iterable, Cache.Entry.class); + assertThat(entries).hasSize(2); + assertThat(iterable).hasSize(2); + assertThat(entries[0].key()[0]).isEqualTo("france"); + assertThat(entries[0].value()).isEqualTo("paris"); + assertThat(entries[1].key()[0]).isEqualTo("italy"); + assertThat(entries[1].value()).isEqualTo("rome"); + + cache.remove("france"); + assertThat(cache.get("france")).isNull(); + assertThat(cache.get("italy")).isEqualTo("rome"); + assertThat(cache.keySet()).containsOnly("italy"); + assertThat(cache.keySet("france")).isEmpty(); + assertThat(cache.containsKey("france")).isFalse(); + assertThat(cache.containsKey("italy")).isTrue(); + assertThat(values).containsOnly("rome"); + + cache.clear(); + assertThat(values).isEmpty(); + } + + @Test + public void test_key_being_prefix_of_another_key() throws Exception { + Cache<String> cache = caches.createCache("components"); + + cache.put("struts-el:org.apache.strutsel.taglib.html.ELButtonTag", "the Tag"); + cache.put("struts-el:org.apache.strutsel.taglib.html.ELButtonTagBeanInfo", "the BeanInfo"); + + assertThat(cache.get("struts-el:org.apache.strutsel.taglib.html.ELButtonTag")).isEqualTo("the Tag"); + assertThat(cache.get("struts-el:org.apache.strutsel.taglib.html.ELButtonTagBeanInfo")).isEqualTo("the BeanInfo"); + } + + @Test + public void two_parts_key() { + Cache<String> cache = caches.createCache("capitals"); + + assertThat(cache.get("europe", "france")).isNull(); + + cache.put("europe", "france", "paris"); + cache.put("europe", "italy", "rome"); + cache.put("asia", "china", "pekin"); + assertThat(cache.get("europe")).isNull(); + assertThat(cache.get("europe", "france")).isEqualTo("paris"); + assertThat(cache.get("europe", "italy")).isEqualTo("rome"); + assertThat(cache.get("europe")).isNull(); + assertThat(cache.keySet("europe")).containsOnly("france", "italy"); + assertThat(cache.keySet()).containsOnly("europe", "asia"); + assertThat(cache.containsKey("europe")).isFalse(); + assertThat(cache.containsKey("europe", "france")).isTrue(); + assertThat(cache.containsKey("europe", "spain")).isFalse(); + assertThat(cache.values()).containsOnly("paris", "rome", "pekin"); + assertThat(cache.values("america")).isEmpty(); + assertThat(cache.values("europe")).containsOnly("paris", "rome"); + assertThat(cache.values("oceania")).isEmpty(); + + Iterable<Entry<String>> iterable = cache.entries(); + Cache.Entry[] allEntries = Iterables.toArray(iterable, Cache.Entry.class); + assertThat(allEntries).hasSize(3); + assertThat(iterable).hasSize(3); + assertThat(allEntries[0].key()).isEqualTo(new String[] {"asia", "china"}); + assertThat(allEntries[0].value()).isEqualTo("pekin"); + assertThat(allEntries[1].key()).isEqualTo(new String[] {"europe", "france"}); + assertThat(allEntries[1].value()).isEqualTo("paris"); + assertThat(allEntries[2].key()).isEqualTo(new String[] {"europe", "italy"}); + assertThat(allEntries[2].value()).isEqualTo("rome"); + + Iterable<Entry<String>> iterable2 = cache.entries("europe"); + Cache.Entry[] subEntries = Iterables.toArray(iterable2, Cache.Entry.class); + assertThat(subEntries).hasSize(2); + assertThat(iterable2).hasSize(2); + assertThat(subEntries[0].key()).isEqualTo(new String[] {"europe", "france"}); + assertThat(subEntries[0].value()).isEqualTo("paris"); + assertThat(subEntries[1].key()).isEqualTo(new String[] {"europe", "italy"}); + assertThat(subEntries[1].value()).isEqualTo("rome"); + + cache.remove("europe", "france"); + assertThat(cache.values()).containsOnly("rome", "pekin"); + assertThat(cache.get("europe", "france")).isNull(); + assertThat(cache.get("europe", "italy")).isEqualTo("rome"); + assertThat(cache.containsKey("europe", "france")).isFalse(); + assertThat(cache.keySet("europe")).containsOnly("italy"); + + cache.clear("america"); + assertThat(cache.keySet()).containsOnly("europe", "asia"); + cache.clear(); + assertThat(cache.keySet()).isEmpty(); + } + + @Test + public void three_parts_key() { + Cache<String> cache = caches.createCache("places"); + assertThat(cache.get("europe", "france", "paris")).isNull(); + + cache.put("europe", "france", "paris", "eiffel tower"); + cache.put("europe", "france", "annecy", "lake"); + cache.put("europe", "france", "poitiers", "notre dame"); + cache.put("europe", "italy", "rome", "colosseum"); + cache.put("europe2", "ukrania", "kiev", "dunno"); + cache.put("asia", "china", "pekin", "great wall"); + cache.put("america", "us", "new york", "empire state building"); + assertThat(cache.get("europe")).isNull(); + assertThat(cache.get("europe", "france")).isNull(); + assertThat(cache.get("europe", "france", "paris")).isEqualTo("eiffel tower"); + assertThat(cache.get("europe", "france", "annecy")).isEqualTo("lake"); + assertThat(cache.get("europe", "italy", "rome")).isEqualTo("colosseum"); + assertThat(cache.keySet()).containsOnly("europe", "asia", "america", "europe2"); + assertThat(cache.keySet("europe")).containsOnly("france", "italy"); + assertThat(cache.keySet("europe", "france")).containsOnly("annecy", "paris", "poitiers"); + assertThat(cache.containsKey("europe")).isFalse(); + assertThat(cache.containsKey("europe", "france")).isFalse(); + assertThat(cache.containsKey("europe", "france", "annecy")).isTrue(); + assertThat(cache.containsKey("europe", "france", "biarritz")).isFalse(); + assertThat(cache.values()).containsOnly("eiffel tower", "lake", "colosseum", "notre dame", "great wall", "empire state building", "dunno"); + assertThat(cache.values("europe")).containsOnly("eiffel tower", "lake", "colosseum", "notre dame"); + assertThat(cache.values("europe", "france")).containsOnly("eiffel tower", "lake", "notre dame"); + + Iterable<Entry<String>> iterable = cache.entries(); + Cache.Entry[] allEntries = Iterables.toArray(iterable, Cache.Entry.class); + assertThat(allEntries).hasSize(7); + assertThat(iterable).hasSize(7); + assertThat(allEntries[0].key()).isEqualTo(new String[] {"america", "us", "new york"}); + assertThat(allEntries[0].value()).isEqualTo("empire state building"); + assertThat(allEntries[1].key()).isEqualTo(new String[] {"asia", "china", "pekin"}); + assertThat(allEntries[1].value()).isEqualTo("great wall"); + assertThat(allEntries[2].key()).isEqualTo(new String[] {"europe", "france", "annecy"}); + assertThat(allEntries[2].value()).isEqualTo("lake"); + assertThat(allEntries[3].key()).isEqualTo(new String[] {"europe", "france", "paris"}); + assertThat(allEntries[3].value()).isEqualTo("eiffel tower"); + assertThat(allEntries[4].key()).isEqualTo(new String[] {"europe", "france", "poitiers"}); + assertThat(allEntries[4].value()).isEqualTo("notre dame"); + assertThat(allEntries[5].key()).isEqualTo(new String[] {"europe", "italy", "rome"}); + assertThat(allEntries[5].value()).isEqualTo("colosseum"); + + Iterable<Entry<String>> iterable2 = cache.entries("europe"); + Cache.Entry[] subEntries = Iterables.toArray(iterable2, Cache.Entry.class); + assertThat(subEntries).hasSize(4); + assertThat(iterable2).hasSize(4); + assertThat(subEntries[0].key()).isEqualTo(new String[] {"europe", "france", "annecy"}); + assertThat(subEntries[0].value()).isEqualTo("lake"); + assertThat(subEntries[1].key()).isEqualTo(new String[] {"europe", "france", "paris"}); + assertThat(subEntries[1].value()).isEqualTo("eiffel tower"); + assertThat(subEntries[2].key()).isEqualTo(new String[] {"europe", "france", "poitiers"}); + assertThat(subEntries[2].value()).isEqualTo("notre dame"); + assertThat(subEntries[3].key()).isEqualTo(new String[] {"europe", "italy", "rome"}); + assertThat(subEntries[3].value()).isEqualTo("colosseum"); + + cache.remove("europe", "france", "annecy"); + assertThat(cache.values()).containsOnly("eiffel tower", "colosseum", "notre dame", "great wall", "empire state building", "dunno"); + assertThat(cache.values("europe")).containsOnly("eiffel tower", "colosseum", "notre dame"); + assertThat(cache.values("europe", "france")).containsOnly("eiffel tower", "notre dame"); + assertThat(cache.get("europe", "france", "annecy")).isNull(); + assertThat(cache.get("europe", "italy", "rome")).isEqualTo("colosseum"); + assertThat(cache.containsKey("europe", "france")).isFalse(); + + cache.clear("europe", "italy"); + assertThat(cache.values()).containsOnly("eiffel tower", "notre dame", "great wall", "empire state building", "dunno"); + + cache.clear("europe"); + assertThat(cache.values()).containsOnly("great wall", "empire state building", "dunno"); + + cache.clear(); + assertThat(cache.values()).isEmpty(); + } + + @Test + public void remove_versus_clear() { + Cache<String> cache = caches.createCache("capitals"); + cache.put("europe", "france", "paris"); + cache.put("europe", "italy", "rome"); + + // remove("europe") does not remove sub-keys + cache.remove("europe"); + assertThat(cache.values()).containsOnly("paris", "rome"); + + // clear("europe") removes sub-keys + cache.clear("europe"); + assertThat(cache.values()).isEmpty(); + } + + @Test + public void empty_cache() { + Cache<String> cache = caches.createCache("empty"); + + assertThat(cache.get("foo")).isNull(); + assertThat(cache.get("foo", "bar")).isNull(); + assertThat(cache.get("foo", "bar", "baz")).isNull(); + assertThat(cache.keySet()).isEmpty(); + assertThat(cache.keySet("foo")).isEmpty(); + assertThat(cache.containsKey("foo")).isFalse(); + assertThat(cache.containsKey("foo", "bar")).isFalse(); + assertThat(cache.containsKey("foo", "bar", "baz")).isFalse(); + assertThat(cache.values()).isEmpty(); + assertThat(cache.values("foo")).isEmpty(); + + // do not fail + cache.remove("foo"); + cache.remove("foo", "bar"); + cache.remove("foo", "bar", "baz"); + cache.clear("foo"); + cache.clear("foo", "bar"); + cache.clear("foo", "bar", "baz"); + cache.clear(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/index/CachesManagerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/index/CachesManagerTest.java new file mode 100644 index 00000000000..177c6f1e357 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/index/CachesManagerTest.java @@ -0,0 +1,42 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.index; + +import org.junit.Test; + +import java.io.File; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CachesManagerTest extends AbstractCachesTest { + @Test + public void should_stop_and_clean_temp_dir() { + File tempDir = cachesManager.tempDir(); + assertThat(tempDir).isDirectory().exists(); + assertThat(cachesManager.persistit()).isNotNull(); + assertThat(cachesManager.persistit().isInitialized()).isTrue(); + + cachesManager.stop(); + + assertThat(tempDir).doesNotExist(); + assertThat(cachesManager.tempDir()).isNull(); + assertThat(cachesManager.persistit()).isNull(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/index/CachesTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/index/CachesTest.java new file mode 100644 index 00000000000..2a01ac38025 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/index/CachesTest.java @@ -0,0 +1,89 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.index; + +import java.io.Serializable; + +import com.persistit.exception.PersistitException; +import org.junit.Test; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +public class CachesTest extends AbstractCachesTest { + @Test + public void should_create_cache() { + Cache<Element> cache = caches.createCache("foo"); + assertThat(cache).isNotNull(); + } + + @Test + public void should_not_create_cache_twice() { + caches.<Element>createCache("foo"); + try { + caches.<Element>createCache("foo"); + fail(); + } catch (IllegalStateException e) { + // ok + } + } + + @Test + public void should_clean_resources() { + Cache<String> c = caches.<String>createCache("test1"); + for (int i = 0; i < 1_000_000; i++) { + c.put("a" + i, "a" + i); + } + + caches.stop(); + + // manager continues up + assertThat(cachesManager.persistit().isInitialized()).isTrue(); + + caches = new Caches(cachesManager); + caches.start(); + caches.createCache("test1"); + } + + @Test + public void leak_test() throws PersistitException { + caches.stop(); + + int len = 1 * 1024 * 1024; + StringBuilder sb = new StringBuilder(len); + for (int i = 0; i < len; i++) { + sb.append("a"); + } + + for (int i = 0; i < 3; i++) { + caches = new Caches(cachesManager); + caches.start(); + Cache<String> c = caches.<String>createCache("test" + i); + c.put("key" + i, sb.toString()); + cachesManager.persistit().flush(); + + caches.stop(); + } + } + + private static class Element implements Serializable { + private static final long serialVersionUID = 1L; + + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/index/DefaultIndexTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/index/DefaultIndexTest.java new file mode 100644 index 00000000000..8dae9e19ad0 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/index/DefaultIndexTest.java @@ -0,0 +1,163 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.index; + +import java.io.IOException; +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.MeasuresFilters; +import org.sonar.api.profiles.RulesProfile; +import org.sonar.api.resources.Directory; +import org.sonar.api.resources.File; +import org.sonar.api.resources.Java; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.api.rules.Rule; +import org.sonar.api.rules.RuleFinder; +import org.sonar.api.scan.filesystem.PathResolver; +import org.sonar.batch.DefaultProjectTree; +import org.sonar.batch.scan.measure.MeasureCache; +import org.sonar.batch.sensor.DefaultSensorStorage; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DefaultIndexTest { + + @org.junit.Rule + public TemporaryFolder temp = new TemporaryFolder(); + + DefaultIndex index = null; + Rule rule; + RuleFinder ruleFinder; + Project project; + Project moduleA; + Project moduleB; + Project moduleB1; + + private java.io.File baseDir; + + @Before + public void createIndex() throws IOException { + ruleFinder = mock(RuleFinder.class); + + DefaultProjectTree projectTree = mock(DefaultProjectTree.class); + BatchComponentCache resourceCache = new BatchComponentCache(); + index = new DefaultIndex(resourceCache, projectTree, mock(MeasureCache.class), new PathResolver()); + + baseDir = temp.newFolder(); + project = new Project("project"); + when(projectTree.getProjectDefinition(project)).thenReturn(ProjectDefinition.create().setBaseDir(baseDir)); + moduleA = new Project("moduleA").setParent(project); + when(projectTree.getProjectDefinition(moduleA)).thenReturn(ProjectDefinition.create().setBaseDir(new java.io.File(baseDir, "moduleA"))); + moduleB = new Project("moduleB").setParent(project); + when(projectTree.getProjectDefinition(moduleB)).thenReturn(ProjectDefinition.create().setBaseDir(new java.io.File(baseDir, "moduleB"))); + moduleB1 = new Project("moduleB1").setParent(moduleB); + when(projectTree.getProjectDefinition(moduleB1)).thenReturn(ProjectDefinition.create().setBaseDir(new java.io.File(baseDir, "moduleB/moduleB1"))); + + RulesProfile rulesProfile = RulesProfile.create(); + rule = Rule.create("repoKey", "ruleKey", "Rule"); + rule.setId(1); + rulesProfile.activateRule(rule, null); + index.setCurrentProject(project, mock(DefaultSensorStorage.class)); + index.doStart(project); + } + + @Test + public void shouldIndexParentOfDeprecatedFiles() { + File file = File.create("src/org/foo/Bar.java", null, false); + assertThat(index.index(file)).isTrue(); + + Directory reference = Directory.create("src/org/foo"); + assertThat(index.getResource(reference).getName()).isEqualTo("src/org/foo"); + assertThat(index.isIndexed(reference, true)).isTrue(); + assertThat(index.isExcluded(reference)).isFalse(); + assertThat(index.getChildren(reference)).hasSize(1); + assertThat(index.getParent(reference)).isInstanceOf(Project.class); + } + + @Test + public void shouldIndexTreeOfResources() { + Directory directory = Directory.create("src/org/foo"); + File file = File.create("src/org/foo/Bar.java", Java.INSTANCE, false); + + assertThat(index.index(directory)).isTrue(); + assertThat(index.index(file, directory)).isTrue(); + + File fileRef = File.create("src/org/foo/Bar.java", null, false); + assertThat(index.getResource(fileRef).getKey()).isEqualTo("src/org/foo/Bar.java"); + assertThat(index.getResource(fileRef).getLanguage().getKey()).isEqualTo("java"); + assertThat(index.isIndexed(fileRef, true)).isTrue(); + assertThat(index.isExcluded(fileRef)).isFalse(); + assertThat(index.getChildren(fileRef)).isEmpty(); + assertThat(index.getParent(fileRef)).isInstanceOf(Directory.class); + } + + @Test + public void shouldGetSource() throws Exception { + Directory directory = Directory.create("src/org/foo"); + File file = File.create("src/org/foo/Bar.java", Java.INSTANCE, false); + FileUtils.write(new java.io.File(baseDir, "src/org/foo/Bar.java"), "Foo bar"); + + assertThat(index.index(directory)).isTrue(); + assertThat(index.index(file, directory)).isTrue(); + + File fileRef = File.create("src/org/foo/Bar.java", null, false); + assertThat(index.getSource(fileRef)).isEqualTo("Foo bar"); + } + + @Test + public void shouldNotIndexResourceIfParentNotIndexed() { + Directory directory = Directory.create("src/org/other"); + File file = File.create("src/org/foo/Bar.java", null, false); + + assertThat(index.index(file, directory)).isFalse(); + + File fileRef = File.create("src/org/foo/Bar.java", null, false); + assertThat(index.isIndexed(directory, true)).isFalse(); + assertThat(index.isIndexed(fileRef, true)).isFalse(); + assertThat(index.isExcluded(fileRef)).isFalse(); + assertThat(index.getChildren(fileRef)).isEmpty(); + assertThat(index.getParent(fileRef)).isNull(); + } + + @Test + public void shouldNotIndexResourceWhenAddingMeasure() { + Resource dir = Directory.create("src/org/foo"); + index.addMeasure(dir, new Measure("ncloc").setValue(50.0)); + + assertThat(index.isIndexed(dir, true)).isFalse(); + assertThat(index.getMeasures(dir, MeasuresFilters.metric("ncloc"))).isNull(); + } + + @Test + public void shouldComputePathOfIndexedModules() { + assertThat(index.getResource(project).getPath()).isNull(); + assertThat(index.getResource(moduleA).getPath()).isEqualTo("moduleA"); + assertThat(index.getResource(moduleB).getPath()).isEqualTo("moduleB"); + assertThat(index.getResource(moduleB1).getPath()).isEqualTo("moduleB1"); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/DefaultFilterableIssueTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/DefaultFilterableIssueTest.java new file mode 100644 index 00000000000..daded262498 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/DefaultFilterableIssueTest.java @@ -0,0 +1,86 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import java.util.Date; + +import static org.mockito.Mockito.mock; + +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.resources.Project; +import org.sonar.scanner.protocol.Constants.Severity; +import org.sonar.scanner.protocol.output.ScannerReport.Issue; + +public class DefaultFilterableIssueTest { + private DefaultFilterableIssue issue; + private Project mockedProject; + private String componentKey; + private Issue rawIssue; + + @Before + public void setUp() { + mockedProject = mock(Project.class); + componentKey = "component"; + } + + private Issue createIssue() { + Issue.Builder builder = Issue.newBuilder(); + + builder.setGap(3.0); + builder.setLine(30); + builder.setSeverity(Severity.MAJOR); + return builder.build(); + } + + private Issue createIssueWithoutFields() { + Issue.Builder builder = Issue.newBuilder(); + builder.setSeverity(Severity.MAJOR); + return builder.build(); + } + + @Test + public void testRoundTrip() { + rawIssue = createIssue(); + issue = new DefaultFilterableIssue(mockedProject, rawIssue, componentKey); + + when(mockedProject.getAnalysisDate()).thenReturn(new Date(10_000)); + when(mockedProject.getEffectiveKey()).thenReturn("projectKey"); + + assertThat(issue.componentKey()).isEqualTo(componentKey); + assertThat(issue.creationDate()).isEqualTo(new Date(10_000)); + assertThat(issue.line()).isEqualTo(30); + assertThat(issue.projectKey()).isEqualTo("projectKey"); + assertThat(issue.effortToFix()).isEqualTo(3.0); + assertThat(issue.severity()).isEqualTo("MAJOR"); + } + + @Test + public void nullValues() { + rawIssue = createIssueWithoutFields(); + issue = new DefaultFilterableIssue(mockedProject, rawIssue, componentKey); + + assertThat(issue.line()).isNull(); + assertThat(issue.effortToFix()).isNull(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/DefaultIssueCallbackTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/DefaultIssueCallbackTest.java new file mode 100644 index 00000000000..7be36bd3bc5 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/DefaultIssueCallbackTest.java @@ -0,0 +1,137 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.sonar.batch.issue.tracking.TrackedIssue; + +import org.sonar.api.batch.rule.Rule; +import org.sonar.api.rule.RuleKey; +import org.sonar.batch.bootstrapper.IssueListener.Issue; +import org.mockito.MockitoAnnotations; +import org.mockito.Mock; +import org.sonar.api.batch.rule.Rules; +import org.sonar.batch.repository.user.UserRepositoryLoader; +import org.sonar.scanner.protocol.input.ScannerInput; +import org.sonar.batch.bootstrapper.IssueListener; +import org.junit.Before; +import com.google.common.collect.ImmutableList; + +import java.util.LinkedList; +import java.util.List; + +import static org.mockito.Matchers.any; +import static org.assertj.core.api.Assertions.assertThat; +import org.junit.Test; + +public class DefaultIssueCallbackTest { + @Mock + private IssueCache issueCache; + @Mock + private UserRepositoryLoader userRepository; + @Mock + private Rules rules; + + private TrackedIssue issue; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + RuleKey ruleKey = RuleKey.of("repo", "key"); + issue = new TrackedIssue(); + issue.setKey("key"); + issue.setAssignee("user"); + issue.setRuleKey(ruleKey); + + when(issueCache.all()).thenReturn(ImmutableList.of(issue)); + + ScannerInput.User.Builder userBuilder = ScannerInput.User.newBuilder(); + userBuilder.setLogin("user"); + userBuilder.setName("name"); + when(userRepository.load("user")).thenReturn(userBuilder.build()); + + Rule r = mock(Rule.class); + when(r.name()).thenReturn("rule name"); + when(rules.find(ruleKey)).thenReturn(r); + } + + @Test + public void testWithoutListener() { + DefaultIssueCallback issueCallback = new DefaultIssueCallback(issueCache, userRepository, rules); + issueCallback.execute(); + } + + @Test + public void testWithListener() { + final List<IssueListener.Issue> issueList = new LinkedList<>(); + IssueListener listener = new IssueListener() { + @Override + public void handle(Issue issue) { + issueList.add(issue); + } + }; + + DefaultIssueCallback issueCallback = new DefaultIssueCallback(issueCache, listener, userRepository, rules); + issueCallback.execute(); + + assertThat(issueList).hasSize(1); + Issue callbackIssue = issueList.get(0); + + assertThat(callbackIssue.getAssigneeName()).isEqualTo("name"); + assertThat(callbackIssue.getRuleName()).isEqualTo("rule name"); + } + + @Test + public void testWithNulls() { + final List<IssueListener.Issue> issueList = new LinkedList<>(); + IssueListener listener = new IssueListener() { + @Override + public void handle(Issue issue) { + issueList.add(issue); + } + }; + + issue.setKey(null); + issue.setAssignee(null); + + DefaultIssueCallback issueCallback = new DefaultIssueCallback(issueCache, listener, userRepository, rules); + issueCallback.execute(); + } + + @Test + public void testDecorationNotFound() { + final List<IssueListener.Issue> issueList = new LinkedList<>(); + IssueListener listener = new IssueListener() { + @Override + public void handle(Issue issue) { + issueList.add(issue); + } + }; + + when(userRepository.load(any(String.class))).thenReturn(null); + when(rules.find(any(RuleKey.class))).thenReturn(null); + + DefaultIssueCallback issueCallback = new DefaultIssueCallback(issueCache, listener, userRepository, rules); + issueCallback.execute(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/DefaultIssueFilterChainTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/DefaultIssueFilterChainTest.java new file mode 100644 index 00000000000..041bc8493a2 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/DefaultIssueFilterChainTest.java @@ -0,0 +1,95 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import org.sonar.api.scan.issue.filter.FilterableIssue; + +import org.junit.Test; +import org.sonar.api.scan.issue.filter.IssueFilter; +import org.sonar.api.scan.issue.filter.IssueFilterChain; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; + +public class DefaultIssueFilterChainTest { + private final FilterableIssue issue = mock(FilterableIssue.class); + + @Test + public void should_accept_when_no_filter() { + assertThat(new DefaultIssueFilterChain().accept(issue)).isTrue(); + } + + class PassingFilter implements IssueFilter { + @Override + public boolean accept(FilterableIssue issue, IssueFilterChain chain) { + return chain.accept(issue); + } + } + + class AcceptingFilter implements IssueFilter { + @Override + public boolean accept(FilterableIssue issue, IssueFilterChain chain) { + return true; + } + } + + class RefusingFilter implements IssueFilter { + @Override + public boolean accept(FilterableIssue issue, IssueFilterChain chain) { + return false; + } + } + + class FailingFilter implements IssueFilter { + @Override + public boolean accept(FilterableIssue issue, IssueFilterChain chain) { + fail(); + return false; + } + + } + + @Test + public void should_accept_if_all_filters_pass() { + assertThat(new DefaultIssueFilterChain( + new PassingFilter(), + new PassingFilter(), + new PassingFilter() + ).accept(issue)).isTrue(); + } + + @Test + public void should_accept_and_not_go_further_if_filter_accepts() { + assertThat(new DefaultIssueFilterChain( + new PassingFilter(), + new AcceptingFilter(), + new FailingFilter() + ).accept(issue)).isTrue(); + } + + @Test + public void should_refuse_and_not_go_further_if_filter_refuses() { + assertThat(new DefaultIssueFilterChain( + new PassingFilter(), + new RefusingFilter(), + new FailingFilter() + ).accept(issue)).isFalse(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/DefaultProjectIssuesTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/DefaultProjectIssuesTest.java new file mode 100644 index 00000000000..9448206b1d6 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/DefaultProjectIssuesTest.java @@ -0,0 +1,78 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import org.sonar.batch.issue.tracking.TrackedIssue; + +import com.google.common.collect.Lists; +import org.junit.Test; +import org.sonar.api.issue.Issue; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rule.Severity; + +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DefaultProjectIssuesTest { + + static final RuleKey SQUID_RULE_KEY = RuleKey.of("squid", "AvoidCycle"); + + IssueCache cache = mock(IssueCache.class); + DefaultProjectIssues projectIssues = new DefaultProjectIssues(cache); + + @Test + public void should_get_all_issues() { + DefaultIssue issueOnModule = new DefaultIssue().setKey("1").setRuleKey(SQUID_RULE_KEY).setComponentKey("org.apache:struts-core"); + DefaultIssue issueInModule = new DefaultIssue().setKey("2").setRuleKey(SQUID_RULE_KEY).setComponentKey("org.apache:struts-core:Action"); + DefaultIssue resolvedIssueInModule = new DefaultIssue().setKey("3").setRuleKey(SQUID_RULE_KEY).setComponentKey("org.apache:struts-core:Action") + .setResolution(Issue.RESOLUTION_FIXED); + + DefaultIssue issueOnRoot = new DefaultIssue().setKey("4").setRuleKey(SQUID_RULE_KEY).setSeverity(Severity.CRITICAL).setComponentKey("org.apache:struts"); + DefaultIssue issueInRoot = new DefaultIssue().setKey("5").setRuleKey(SQUID_RULE_KEY).setSeverity(Severity.CRITICAL).setComponentKey("org.apache:struts:FileInRoot"); + when(cache.all()).thenReturn(Arrays.<TrackedIssue>asList( + toTrackedIssue(issueOnRoot), toTrackedIssue(issueInRoot), + toTrackedIssue(issueOnModule), toTrackedIssue(issueInModule), toTrackedIssue(resolvedIssueInModule) + )); + + // unresolved issues + List<Issue> issues = Lists.newArrayList(projectIssues.issues()); + assertThat(issues).containsOnly(issueOnRoot, issueInRoot, issueInModule, issueOnModule); + + List<Issue> resolvedIssues = Lists.newArrayList(projectIssues.resolvedIssues()); + assertThat(resolvedIssues).containsOnly(resolvedIssueInModule); + } + + private TrackedIssue toTrackedIssue(DefaultIssue issue) { + TrackedIssue trackedIssue = new TrackedIssue(); + + trackedIssue.setKey(issue.key()); + trackedIssue.setRuleKey(issue.ruleKey()); + trackedIssue.setComponentKey(issue.componentKey()); + trackedIssue.setSeverity(issue.severity()); + trackedIssue.setResolution(issue.resolution()); + + return trackedIssue; + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/DeprecatedIssueAdapterForFilterTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/DeprecatedIssueAdapterForFilterTest.java new file mode 100644 index 00000000000..b4818adae0e --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/DeprecatedIssueAdapterForFilterTest.java @@ -0,0 +1,157 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import java.util.Date; +import org.junit.Test; +import org.sonar.api.issue.Issue; +import org.sonar.api.resources.Project; +import org.sonar.api.rule.RuleKey; +import org.sonar.scanner.protocol.Constants.Severity; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +public class DeprecatedIssueAdapterForFilterTest { + + private static final String PROJECT_KEY = "foo"; + private static final Date ANALYSIS_DATE = new Date(); + private static final String COMPONENT_KEY = "foo:src/Foo.java"; + + @Test + public void improve_coverage() { + DeprecatedIssueAdapterForFilter issue = new DeprecatedIssueAdapterForFilter(new Project(PROJECT_KEY).setAnalysisDate(ANALYSIS_DATE), + org.sonar.scanner.protocol.output.ScannerReport.Issue.newBuilder() + .setRuleRepository("repo") + .setRuleKey("key") + .setSeverity(Severity.BLOCKER) + .setMsg("msg") + .build(), + COMPONENT_KEY); + DeprecatedIssueAdapterForFilter issue2 = new DeprecatedIssueAdapterForFilter(new Project(PROJECT_KEY).setAnalysisDate(ANALYSIS_DATE), + org.sonar.scanner.protocol.output.ScannerReport.Issue.newBuilder() + .setRuleRepository("repo") + .setRuleKey("key") + .setSeverity(Severity.BLOCKER) + .setMsg("msg") + .setLine(1) + .setGap(2.0) + .build(), + COMPONENT_KEY); + + try { + issue.key(); + fail("Should be unsupported"); + } catch (Exception e) { + assertThat(e).isExactlyInstanceOf(UnsupportedOperationException.class).hasMessage("Not available for issues filters"); + } + + assertThat(issue.componentKey()).isEqualTo(COMPONENT_KEY); + assertThat(issue.ruleKey()).isEqualTo(RuleKey.of("repo", "key")); + + try { + issue.language(); + fail("Should be unsupported"); + } catch (Exception e) { + assertThat(e).isExactlyInstanceOf(UnsupportedOperationException.class).hasMessage("Not available for issues filters"); + } + + assertThat(issue.severity()).isEqualTo("BLOCKER"); + assertThat(issue.message()).isEqualTo("msg"); + assertThat(issue.line()).isNull(); + assertThat(issue2.line()).isEqualTo(1); + assertThat(issue.effortToFix()).isNull(); + assertThat(issue2.effortToFix()).isEqualTo(2.0); + assertThat(issue.status()).isEqualTo(Issue.STATUS_OPEN); + assertThat(issue.resolution()).isNull(); + + try { + issue.reporter(); + fail("Should be unsupported"); + } catch (Exception e) { + assertThat(e).isExactlyInstanceOf(UnsupportedOperationException.class).hasMessage("Not available for issues filters"); + } + + assertThat(issue.assignee()).isNull(); + assertThat(issue.creationDate()).isEqualTo(ANALYSIS_DATE); + assertThat(issue.updateDate()).isNull(); + assertThat(issue.closeDate()).isNull(); + assertThat(issue.attribute(PROJECT_KEY)).isNull(); + + try { + issue.authorLogin(); + fail("Should be unsupported"); + } catch (Exception e) { + assertThat(e).isExactlyInstanceOf(UnsupportedOperationException.class).hasMessage("Not available for issues filters"); + } + + try { + issue.actionPlanKey(); + fail("Should be unsupported"); + } catch (Exception e) { + assertThat(e).isExactlyInstanceOf(UnsupportedOperationException.class).hasMessage("Not available for issues filters"); + } + + try { + issue.comments(); + fail("Should be unsupported"); + } catch (Exception e) { + assertThat(e).isExactlyInstanceOf(UnsupportedOperationException.class).hasMessage("Not available for issues filters"); + } + + try { + issue.isNew(); + fail("Should be unsupported"); + } catch (Exception e) { + assertThat(e).isExactlyInstanceOf(UnsupportedOperationException.class).hasMessage("Not available for issues filters"); + } + + try { + issue.debt(); + fail("Should be unsupported"); + } catch (Exception e) { + assertThat(e).isExactlyInstanceOf(UnsupportedOperationException.class).hasMessage("Not available for issues filters"); + } + + assertThat(issue.projectKey()).isEqualTo(PROJECT_KEY); + + try { + issue.projectUuid(); + fail("Should be unsupported"); + } catch (Exception e) { + assertThat(e).isExactlyInstanceOf(UnsupportedOperationException.class).hasMessage("Not available for issues filters"); + } + + try { + issue.componentUuid(); + fail("Should be unsupported"); + } catch (Exception e) { + assertThat(e).isExactlyInstanceOf(UnsupportedOperationException.class).hasMessage("Not available for issues filters"); + } + + try { + issue.tags(); + fail("Should be unsupported"); + } catch (Exception e) { + assertThat(e).isExactlyInstanceOf(UnsupportedOperationException.class).hasMessage("Not available for issues filters"); + } + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/DeprecatedIssueFilterChainTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/DeprecatedIssueFilterChainTest.java new file mode 100644 index 00000000000..c80b1d64a82 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/DeprecatedIssueFilterChainTest.java @@ -0,0 +1,96 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import org.junit.Test; +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.batch.IssueFilter; +import org.sonar.api.issue.batch.IssueFilterChain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; + +public class DeprecatedIssueFilterChainTest { + + private final Issue issue = mock(Issue.class); + + @Test + public void should_accept_when_no_filter() { + assertThat(new DeprecatedIssueFilterChain().accept(issue)).isTrue(); + } + + class PassingFilter implements IssueFilter { + @Override + public boolean accept(Issue issue, IssueFilterChain chain) { + return chain.accept(issue); + } + } + + class AcceptingFilter implements IssueFilter { + @Override + public boolean accept(Issue issue, IssueFilterChain chain) { + return true; + } + } + + class RefusingFilter implements IssueFilter { + @Override + public boolean accept(Issue issue, IssueFilterChain chain) { + return false; + } + } + + class FailingFilter implements IssueFilter { + @Override + public boolean accept(Issue issue, IssueFilterChain chain) { + fail(); + return false; + } + + } + + @Test + public void should_accept_if_all_filters_pass() { + assertThat(new DeprecatedIssueFilterChain( + new PassingFilter(), + new PassingFilter(), + new PassingFilter() + ).accept(issue)).isTrue(); + } + + @Test + public void should_accept_and_not_go_further_if_filter_accepts() { + assertThat(new DeprecatedIssueFilterChain( + new PassingFilter(), + new AcceptingFilter(), + new FailingFilter() + ).accept(issue)).isTrue(); + } + + @Test + public void should_refuse_and_not_go_further_if_filter_refuses() { + assertThat(new DeprecatedIssueFilterChain( + new PassingFilter(), + new RefusingFilter(), + new FailingFilter() + ).accept(issue)).isFalse(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/IssuableFactoryTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/IssuableFactoryTest.java new file mode 100644 index 00000000000..65856e1e808 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/IssuableFactoryTest.java @@ -0,0 +1,57 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import org.junit.Test; +import org.sonar.api.issue.Issuable; +import org.sonar.api.resources.File; +import org.sonar.api.resources.Project; +import org.sonar.batch.DefaultProjectTree; +import org.sonar.batch.index.BatchComponent; +import org.sonar.batch.sensor.DefaultSensorContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class IssuableFactoryTest { + + ModuleIssues moduleIssues = mock(ModuleIssues.class); + DefaultProjectTree projectTree = mock(DefaultProjectTree.class); + + @Test + public void file_should_be_issuable() { + IssuableFactory factory = new IssuableFactory(mock(DefaultSensorContext.class)); + BatchComponent component = new BatchComponent(1, File.create("foo/bar.c").setEffectiveKey("foo/bar.c"), null); + Issuable issuable = factory.loadPerspective(Issuable.class, component); + + assertThat(issuable).isNotNull(); + assertThat(issuable.issues()).isEmpty(); + } + + @Test + public void project_should_be_issuable() { + IssuableFactory factory = new IssuableFactory(mock(DefaultSensorContext.class)); + BatchComponent component = new BatchComponent(1, new Project("Foo").setEffectiveKey("foo"), null); + Issuable issuable = factory.loadPerspective(Issuable.class, component); + + assertThat(issuable).isNotNull(); + assertThat(issuable.issues()).isEmpty(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/IssueCacheTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/IssueCacheTest.java new file mode 100644 index 00000000000..6d9f42a8ede --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/IssueCacheTest.java @@ -0,0 +1,98 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import org.sonar.batch.issue.tracking.TrackedIssue; + +import org.sonar.batch.index.AbstractCachesTest; +import com.google.common.base.Function; +import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableList; +import org.junit.Test; +import org.sonar.api.rule.Severity; + +import javax.annotation.Nullable; + +import java.util.Collection; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class IssueCacheTest extends AbstractCachesTest { + + @Test + public void should_add_new_issue() { + IssueCache cache = new IssueCache(caches); + TrackedIssue issue1 = createIssue("111", "org.struts.Action", null); + TrackedIssue issue2 = createIssue("222", "org.struts.Action", null); + TrackedIssue issue3 = createIssue("333", "org.struts.Filter", null); + issue3.setAssignee("foo"); + cache.put(issue1).put(issue2).put(issue3); + + assertThat(issueKeys(cache.byComponent("org.struts.Action"))).containsOnly("111", "222"); + assertThat(issueKeys(cache.byComponent("org.struts.Filter"))).containsOnly("333"); + assertThat(cache.byComponent("org.struts.Filter").iterator().next().assignee()).isEqualTo("foo"); + } + + @Test + public void should_update_existing_issue() { + IssueCache cache = new IssueCache(caches); + TrackedIssue issue = createIssue("111", "org.struts.Action", Severity.BLOCKER); + cache.put(issue); + + issue.setSeverity(Severity.MINOR); + cache.put(issue); + + List<TrackedIssue> issues = ImmutableList.copyOf(cache.byComponent("org.struts.Action")); + assertThat(issues).hasSize(1); + TrackedIssue reloaded = issues.iterator().next(); + assertThat(reloaded.key()).isEqualTo("111"); + assertThat(reloaded.severity()).isEqualTo(Severity.MINOR); + } + + @Test + public void should_get_all_issues() { + IssueCache cache = new IssueCache(caches); + TrackedIssue issue1 = createIssue("111", "org.struts.Action", Severity.BLOCKER); + TrackedIssue issue2 = createIssue("222", "org.struts.Filter", Severity.INFO); + cache.put(issue1).put(issue2); + + List<TrackedIssue> issues = ImmutableList.copyOf(cache.all()); + assertThat(issues).containsOnly(issue1, issue2); + } + + private Collection<String> issueKeys(Iterable<TrackedIssue> issues) { + return Collections2.transform(ImmutableList.copyOf(issues), new Function<TrackedIssue, String>() { + @Override + public String apply(@Nullable TrackedIssue issue) { + return issue.key(); + } + }); + } + + private TrackedIssue createIssue(String key, String componentKey, String severity) { + TrackedIssue issue = new TrackedIssue(); + issue.setKey(key); + issue.setComponentKey(componentKey); + issue.setSeverity(severity); + + return issue; + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java new file mode 100644 index 00000000000..af84563a8b8 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java @@ -0,0 +1,221 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import java.io.StringReader; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.fs.internal.FileMetadata; +import org.sonar.api.batch.rule.internal.ActiveRulesBuilder; +import org.sonar.api.batch.rule.internal.RulesBuilder; +import org.sonar.api.batch.sensor.issue.internal.DefaultIssue; +import org.sonar.api.batch.sensor.issue.internal.DefaultIssueLocation; +import org.sonar.api.resources.File; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rule.Severity; +import org.sonar.api.utils.MessageException; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.report.ReportPublisher; +import org.sonar.scanner.protocol.output.ScannerReport; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class ModuleIssuesTest { + + static final RuleKey SQUID_RULE_KEY = RuleKey.of("squid", "AvoidCycle"); + static final String SQUID_RULE_NAME = "Avoid Cycle"; + + @Mock + IssueFilters filters; + + ActiveRulesBuilder activeRulesBuilder = new ActiveRulesBuilder(); + RulesBuilder ruleBuilder = new RulesBuilder(); + + ModuleIssues moduleIssues; + + BatchComponentCache componentCache = new BatchComponentCache(); + InputFile file = new DefaultInputFile("foo", "src/Foo.php").initMetadata(new FileMetadata().readMetadata(new StringReader("Foo\nBar\nBiz\n"))); + ReportPublisher reportPublisher = mock(ReportPublisher.class, RETURNS_DEEP_STUBS); + + @Before + public void prepare() { + componentCache.add(File.create("src/Foo.php").setEffectiveKey("foo:src/Foo.php"), null).setInputComponent(file); + } + + @Test + public void fail_on_unknown_rule() { + initModuleIssues(); + DefaultIssue issue = new DefaultIssue() + .at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("Foo")) + .forRule(SQUID_RULE_KEY); + try { + moduleIssues.initAndAddIssue(issue); + fail(); + } catch (Exception e) { + assertThat(e).isInstanceOf(MessageException.class); + } + + verifyZeroInteractions(reportPublisher); + } + + @Test + public void fail_if_rule_has_no_name_and_issue_has_no_message() { + ruleBuilder.add(SQUID_RULE_KEY).setInternalKey(SQUID_RULE_KEY.rule()); + initModuleIssues(); + DefaultIssue issue = new DefaultIssue() + .at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("")) + .forRule(SQUID_RULE_KEY); + try { + moduleIssues.initAndAddIssue(issue); + fail(); + } catch (Exception e) { + assertThat(e).isInstanceOf(MessageException.class); + } + + verifyZeroInteractions(reportPublisher); + } + + @Test + public void ignore_null_active_rule() { + ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME); + initModuleIssues(); + DefaultIssue issue = new DefaultIssue() + .at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("Foo")) + .forRule(SQUID_RULE_KEY); + boolean added = moduleIssues.initAndAddIssue(issue); + + assertThat(added).isFalse(); + verifyZeroInteractions(reportPublisher); + } + + @Test + public void ignore_null_rule_of_active_rule() { + ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME); + activeRulesBuilder.create(SQUID_RULE_KEY).activate(); + initModuleIssues(); + + DefaultIssue issue = new DefaultIssue() + .at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("Foo")) + .forRule(SQUID_RULE_KEY); + boolean added = moduleIssues.initAndAddIssue(issue); + + assertThat(added).isFalse(); + verifyZeroInteractions(reportPublisher); + } + + @Test + public void add_issue_to_cache() { + ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME); + activeRulesBuilder.create(SQUID_RULE_KEY).setSeverity(Severity.INFO).activate(); + initModuleIssues(); + + DefaultIssue issue = new DefaultIssue() + .at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("Foo")) + .forRule(SQUID_RULE_KEY) + .overrideSeverity(org.sonar.api.batch.rule.Severity.CRITICAL); + + when(filters.accept(anyString(), any(ScannerReport.Issue.class))).thenReturn(true); + + boolean added = moduleIssues.initAndAddIssue(issue); + + assertThat(added).isTrue(); + ArgumentCaptor<ScannerReport.Issue> argument = ArgumentCaptor.forClass(ScannerReport.Issue.class); + verify(reportPublisher.getWriter()).appendComponentIssue(eq(1), argument.capture()); + assertThat(argument.getValue().getSeverity()).isEqualTo(org.sonar.scanner.protocol.Constants.Severity.CRITICAL); + } + + @Test + public void use_severity_from_active_rule_if_no_severity_on_issue() { + ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME); + activeRulesBuilder.create(SQUID_RULE_KEY).setSeverity(Severity.INFO).activate(); + initModuleIssues(); + + DefaultIssue issue = new DefaultIssue() + .at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("Foo")) + .forRule(SQUID_RULE_KEY); + when(filters.accept(anyString(), any(ScannerReport.Issue.class))).thenReturn(true); + moduleIssues.initAndAddIssue(issue); + + ArgumentCaptor<ScannerReport.Issue> argument = ArgumentCaptor.forClass(ScannerReport.Issue.class); + verify(reportPublisher.getWriter()).appendComponentIssue(eq(1), argument.capture()); + assertThat(argument.getValue().getSeverity()).isEqualTo(org.sonar.scanner.protocol.Constants.Severity.INFO); + } + + @Test + public void use_rule_name_if_no_message() { + ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME); + activeRulesBuilder.create(SQUID_RULE_KEY).setSeverity(Severity.INFO).setName(SQUID_RULE_NAME).activate(); + initModuleIssues(); + + DefaultIssue issue = new DefaultIssue() + .at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("")) + .forRule(SQUID_RULE_KEY); + when(filters.accept(anyString(), any(ScannerReport.Issue.class))).thenReturn(true); + + boolean added = moduleIssues.initAndAddIssue(issue); + + assertThat(added).isTrue(); + ArgumentCaptor<ScannerReport.Issue> argument = ArgumentCaptor.forClass(ScannerReport.Issue.class); + verify(reportPublisher.getWriter()).appendComponentIssue(eq(1), argument.capture()); + assertThat(argument.getValue().getMsg()).isEqualTo("Avoid Cycle"); + } + + @Test + public void filter_issue() { + ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME); + activeRulesBuilder.create(SQUID_RULE_KEY).setSeverity(Severity.INFO).activate(); + initModuleIssues(); + + DefaultIssue issue = new DefaultIssue() + .at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("")) + .forRule(SQUID_RULE_KEY); + + when(filters.accept(anyString(), any(ScannerReport.Issue.class))).thenReturn(false); + + boolean added = moduleIssues.initAndAddIssue(issue); + + assertThat(added).isFalse(); + verifyZeroInteractions(reportPublisher); + } + + /** + * Every rules and active rules has to be added in builders before creating ModuleIssues + */ + private void initModuleIssues() { + moduleIssues = new ModuleIssues(activeRulesBuilder.build(), ruleBuilder.build(), filters, reportPublisher, componentCache); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/TrackedIssueAdapterTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/TrackedIssueAdapterTest.java new file mode 100644 index 00000000000..11fc560b318 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/TrackedIssueAdapterTest.java @@ -0,0 +1,84 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue; + +import java.util.Date; +import org.junit.Test; +import org.sonar.api.issue.Issue; +import org.sonar.api.rule.RuleKey; +import org.sonar.batch.issue.tracking.TrackedIssue; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TrackedIssueAdapterTest { + + @Test + public void improve_coverage() { + Date creationDate = new Date(); + TrackedIssue trackedIssue = new TrackedIssue() + .setKey("XYZ123") + .setComponentKey("foo") + .setRuleKey(RuleKey.of("repo", "rule")) + .setSeverity("MAJOR") + .setMessage("msg") + .setStartLine(1) + .setGap(2.0) + .setStatus("RESOLVED") + .setResolution("FIXED") + .setReporter("toto") + .setAssignee("tata") + .setNew(true) + .setCreationDate(creationDate); + Issue issue = new TrackedIssueAdapter(trackedIssue); + assertThat(issue.key()).isEqualTo("XYZ123"); + assertThat(issue.componentKey()).isEqualTo("foo"); + assertThat(issue.ruleKey()).isEqualTo(RuleKey.of("repo", "rule")); + assertThat(issue.severity()).isEqualTo("MAJOR"); + assertThat(issue.message()).isEqualTo("msg"); + assertThat(issue.line()).isEqualTo(1); + assertThat(issue.effortToFix()).isEqualTo(2.0); + assertThat(issue.status()).isEqualTo("RESOLVED"); + assertThat(issue.resolution()).isEqualTo("FIXED"); + assertThat(issue.reporter()).isEqualTo("toto"); + assertThat(issue.assignee()).isEqualTo("tata"); + assertThat(issue.isNew()).isTrue(); + assertThat(issue.attribute("foo")).isNull(); + assertThat(issue.creationDate()).isEqualTo(creationDate); + assertThat(issue.language()).isNull(); + assertThat(issue.updateDate()).isNull(); + assertThat(issue.closeDate()).isNull(); + assertThat(issue.authorLogin()).isNull(); + assertThat(issue.actionPlanKey()).isNull(); + assertThat(issue.comments()).isEmpty(); + assertThat(issue.debt()).isNull(); + assertThat(issue.projectKey()).isNull(); + assertThat(issue.projectUuid()).isNull(); + assertThat(issue.componentUuid()).isNull(); + assertThat(issue.tags()).isEmpty(); + + assertThat(issue).isNotEqualTo(null); + assertThat(issue).isNotEqualTo("Foo"); + assertThat(issue).isEqualTo(new TrackedIssueAdapter(trackedIssue)); + assertThat(issue.hashCode()).isEqualTo(trackedIssue.key().hashCode()); + assertThat(issue).isNotEqualTo(new TrackedIssueAdapter(new TrackedIssue() + .setKey("another"))); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/EnforceIssuesFilterTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/EnforceIssuesFilterTest.java new file mode 100644 index 00000000000..28df728657d --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/EnforceIssuesFilterTest.java @@ -0,0 +1,149 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.ignore; + +import org.sonar.api.scan.issue.filter.FilterableIssue; + +import com.google.common.collect.ImmutableList; +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.scan.issue.filter.IssueFilterChain; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.utils.WildcardPattern; +import org.sonar.batch.issue.ignore.pattern.IssueInclusionPatternInitializer; +import org.sonar.batch.issue.ignore.pattern.IssuePattern; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +public class EnforceIssuesFilterTest { + + private IssueInclusionPatternInitializer exclusionPatternInitializer; + private EnforceIssuesFilter ignoreFilter; + private FilterableIssue issue; + private IssueFilterChain chain; + + @Before + public void init() { + exclusionPatternInitializer = mock(IssueInclusionPatternInitializer.class); + issue = mock(FilterableIssue.class); + chain = mock(IssueFilterChain.class); + when(chain.accept(issue)).thenReturn(true); + + ignoreFilter = new EnforceIssuesFilter(exclusionPatternInitializer); + } + + @Test + public void shouldPassToChainIfNoConfiguredPatterns() { + assertThat(ignoreFilter.accept(issue, chain)).isTrue(); + verify(chain).accept(issue); + } + + @Test + public void shouldPassToChainIfRuleDoesNotMatch() { + String rule = "rule"; + RuleKey ruleKey = mock(RuleKey.class); + when(ruleKey.toString()).thenReturn(rule); + when(issue.ruleKey()).thenReturn(ruleKey); + + IssuePattern matching = mock(IssuePattern.class); + WildcardPattern rulePattern = mock(WildcardPattern.class); + when(matching.getRulePattern()).thenReturn(rulePattern); + when(rulePattern.match(rule)).thenReturn(false); + when(exclusionPatternInitializer.getMulticriteriaPatterns()).thenReturn(ImmutableList.of(matching)); + + assertThat(ignoreFilter.accept(issue, chain)).isTrue(); + verify(chain).accept(issue); + } + + @Test + public void shouldAcceptIssueIfFullyMatched() { + String rule = "rule"; + String path = "org/sonar/api/Issue.java"; + String componentKey = "org.sonar.api.Issue"; + RuleKey ruleKey = mock(RuleKey.class); + when(ruleKey.toString()).thenReturn(rule); + when(issue.ruleKey()).thenReturn(ruleKey); + when(issue.componentKey()).thenReturn(componentKey); + + IssuePattern matching = mock(IssuePattern.class); + WildcardPattern rulePattern = mock(WildcardPattern.class); + when(matching.getRulePattern()).thenReturn(rulePattern); + when(rulePattern.match(rule)).thenReturn(true); + WildcardPattern pathPattern = mock(WildcardPattern.class); + when(matching.getResourcePattern()).thenReturn(pathPattern); + when(pathPattern.match(path)).thenReturn(true); + when(exclusionPatternInitializer.getMulticriteriaPatterns()).thenReturn(ImmutableList.of(matching)); + when(exclusionPatternInitializer.getPathForComponent(componentKey)).thenReturn(path); + + assertThat(ignoreFilter.accept(issue, chain)).isTrue(); + verifyZeroInteractions(chain); + } + + @Test + public void shouldRefuseIssueIfRuleMatchesButNotPath() { + String rule = "rule"; + String path = "org/sonar/api/Issue.java"; + String componentKey = "org.sonar.api.Issue"; + RuleKey ruleKey = mock(RuleKey.class); + when(ruleKey.toString()).thenReturn(rule); + when(issue.ruleKey()).thenReturn(ruleKey); + when(issue.componentKey()).thenReturn(componentKey); + + IssuePattern matching = mock(IssuePattern.class); + WildcardPattern rulePattern = mock(WildcardPattern.class); + when(matching.getRulePattern()).thenReturn(rulePattern); + when(rulePattern.match(rule)).thenReturn(true); + WildcardPattern pathPattern = mock(WildcardPattern.class); + when(matching.getResourcePattern()).thenReturn(pathPattern); + when(pathPattern.match(path)).thenReturn(false); + when(exclusionPatternInitializer.getMulticriteriaPatterns()).thenReturn(ImmutableList.of(matching)); + when(exclusionPatternInitializer.getPathForComponent(componentKey)).thenReturn(path); + + assertThat(ignoreFilter.accept(issue, chain)).isFalse(); + verifyZeroInteractions(chain); + } + + @Test + public void shouldRefuseIssueIfRuleMatchesAndPathUnknown() { + String rule = "rule"; + String path = "org/sonar/api/Issue.java"; + String componentKey = "org.sonar.api.Issue"; + RuleKey ruleKey = mock(RuleKey.class); + when(ruleKey.toString()).thenReturn(rule); + when(issue.ruleKey()).thenReturn(ruleKey); + when(issue.componentKey()).thenReturn(componentKey); + + IssuePattern matching = mock(IssuePattern.class); + WildcardPattern rulePattern = mock(WildcardPattern.class); + when(matching.getRulePattern()).thenReturn(rulePattern); + when(rulePattern.match(rule)).thenReturn(true); + WildcardPattern pathPattern = mock(WildcardPattern.class); + when(matching.getResourcePattern()).thenReturn(pathPattern); + when(pathPattern.match(path)).thenReturn(false); + when(exclusionPatternInitializer.getMulticriteriaPatterns()).thenReturn(ImmutableList.of(matching)); + when(exclusionPatternInitializer.getPathForComponent(componentKey)).thenReturn(null); + + assertThat(ignoreFilter.accept(issue, chain)).isFalse(); + verifyZeroInteractions(chain); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/IgnoreIssuesFilterTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/IgnoreIssuesFilterTest.java new file mode 100644 index 00000000000..6cf431e215c --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/IgnoreIssuesFilterTest.java @@ -0,0 +1,67 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.ignore; + +import org.sonar.api.scan.issue.filter.FilterableIssue; + +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.scan.issue.filter.IssueFilterChain; +import org.sonar.batch.issue.ignore.pattern.IssueExclusionPatternInitializer; +import org.sonar.batch.issue.ignore.pattern.IssuePattern; +import org.sonar.batch.issue.ignore.pattern.PatternMatcher; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class IgnoreIssuesFilterTest { + + private IssueExclusionPatternInitializer exclusionPatternInitializer; + private PatternMatcher exclusionPatternMatcher; + private IgnoreIssuesFilter ignoreFilter; + private FilterableIssue issue; + private IssueFilterChain chain; + + @Before + public void init() { + exclusionPatternMatcher = mock(PatternMatcher.class); + exclusionPatternInitializer = mock(IssueExclusionPatternInitializer.class); + when(exclusionPatternInitializer.getPatternMatcher()).thenReturn(exclusionPatternMatcher); + issue = mock(FilterableIssue.class); + chain = mock(IssueFilterChain.class); + when(chain.accept(issue)).thenReturn(true); + + ignoreFilter = new IgnoreIssuesFilter(exclusionPatternInitializer); + } + + @Test + public void shouldPassToChainIfMatcherHasNoPatternForIssue() { + when(exclusionPatternMatcher.getMatchingPattern(issue)).thenReturn(null); + + assertThat(ignoreFilter.accept(issue, chain)).isTrue(); + } + + @Test + public void shouldAcceptOrRefuseIfMatcherHasPatternForIssue() { + when(exclusionPatternMatcher.getMatchingPattern(issue)).thenReturn(mock(IssuePattern.class)); + + assertThat(ignoreFilter.accept(issue, chain)).isFalse(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/pattern/IssueExclusionPatternInitializerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/pattern/IssueExclusionPatternInitializerTest.java new file mode 100644 index 00000000000..13e1646bb9d --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/pattern/IssueExclusionPatternInitializerTest.java @@ -0,0 +1,141 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.ignore.pattern; + + +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.config.PropertyDefinitions; +import org.sonar.api.config.Settings; +import org.sonar.api.utils.SonarException; +import org.sonar.core.config.IssueExclusionProperties; + +import static org.assertj.core.api.Assertions.assertThat; + +public class IssueExclusionPatternInitializerTest { + + private IssueExclusionPatternInitializer patternsInitializer; + + private Settings settings; + + @Before + public void init() { + settings = new Settings(new PropertyDefinitions(IssueExclusionProperties.all())); + patternsInitializer = new IssueExclusionPatternInitializer(settings); + } + + @Test + public void testNoConfiguration() { + patternsInitializer.initPatterns(); + assertThat(patternsInitializer.hasConfiguredPatterns()).isFalse(); + assertThat(patternsInitializer.getMulticriteriaPatterns().size()).isEqualTo(0); + } + + @Test + public void shouldHavePatternsBasedOnMulticriteriaPattern() { + settings.setProperty("sonar.issue.ignore" + ".multicriteria", "1,2"); + settings.setProperty("sonar.issue.ignore" + ".multicriteria" + ".1." + "resourceKey", "org/foo/Bar.java"); + settings.setProperty("sonar.issue.ignore" + ".multicriteria" + ".1." + "ruleKey", "*"); + settings.setProperty("sonar.issue.ignore" + ".multicriteria" + ".2." + "resourceKey", "org/foo/Hello.java"); + settings.setProperty("sonar.issue.ignore" + ".multicriteria" + ".2." + "ruleKey", "checkstyle:MagicNumber"); + patternsInitializer.initPatterns(); + + assertThat(patternsInitializer.hasConfiguredPatterns()).isTrue(); + assertThat(patternsInitializer.hasFileContentPattern()).isFalse(); + assertThat(patternsInitializer.hasMulticriteriaPatterns()).isTrue(); + assertThat(patternsInitializer.getMulticriteriaPatterns().size()).isEqualTo(2); + assertThat(patternsInitializer.getBlockPatterns().size()).isEqualTo(0); + assertThat(patternsInitializer.getAllFilePatterns().size()).isEqualTo(0); + + patternsInitializer.initializePatternsForPath("org/foo/Bar.java", "org.foo.Bar"); + patternsInitializer.initializePatternsForPath("org/foo/Baz.java", "org.foo.Baz"); + patternsInitializer.initializePatternsForPath("org/foo/Hello.java", "org.foo.Hello"); + + assertThat(patternsInitializer.getPatternMatcher().getPatternsForComponent("org.foo.Bar")).hasSize(1); + assertThat(patternsInitializer.getPatternMatcher().getPatternsForComponent("org.foo.Baz")).hasSize(0); + assertThat(patternsInitializer.getPatternMatcher().getPatternsForComponent("org.foo.Hello")).hasSize(1); + + } + + @Test(expected = SonarException.class) + public void shouldLogInvalidResourceKey() { + settings.setProperty("sonar.issue.ignore" + ".multicriteria", "1"); + settings.setProperty("sonar.issue.ignore" + ".multicriteria" + ".1." + "resourceKey", ""); + settings.setProperty("sonar.issue.ignore" + ".multicriteria" + ".1." + "ruleKey", "*"); + patternsInitializer.initPatterns(); + } + + @Test(expected = SonarException.class) + public void shouldLogInvalidRuleKey() { + settings.setProperty("sonar.issue.ignore" + ".multicriteria", "1"); + settings.setProperty("sonar.issue.ignore" + ".multicriteria" + ".1." + "resourceKey", "*"); + settings.setProperty("sonar.issue.ignore" + ".multicriteria" + ".1." + "ruleKey", ""); + patternsInitializer.initPatterns(); + } + + @Test + public void shouldReturnBlockPattern() { + settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY, "1,2,3"); + settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY + ".1." + IssueExclusionProperties.BEGIN_BLOCK_REGEXP, "// SONAR-OFF"); + settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY + ".1." + IssueExclusionProperties.END_BLOCK_REGEXP, "// SONAR-ON"); + settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY + ".2." + IssueExclusionProperties.BEGIN_BLOCK_REGEXP, "// FOO-OFF"); + settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY + ".2." + IssueExclusionProperties.END_BLOCK_REGEXP, "// FOO-ON"); + settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY + ".3." + IssueExclusionProperties.BEGIN_BLOCK_REGEXP, "// IGNORE-TO-EOF"); + settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY + ".3." + IssueExclusionProperties.END_BLOCK_REGEXP, ""); + patternsInitializer.loadFileContentPatterns(); + + assertThat(patternsInitializer.hasConfiguredPatterns()).isTrue(); + assertThat(patternsInitializer.hasFileContentPattern()).isTrue(); + assertThat(patternsInitializer.hasMulticriteriaPatterns()).isFalse(); + assertThat(patternsInitializer.getMulticriteriaPatterns().size()).isEqualTo(0); + assertThat(patternsInitializer.getBlockPatterns().size()).isEqualTo(3); + assertThat(patternsInitializer.getAllFilePatterns().size()).isEqualTo(0); + } + + @Test(expected = SonarException.class) + public void shouldLogInvalidStartBlockPattern() { + settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY, "1"); + settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY + ".1." + IssueExclusionProperties.BEGIN_BLOCK_REGEXP, ""); + settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY + ".1." + IssueExclusionProperties.END_BLOCK_REGEXP, "// SONAR-ON"); + patternsInitializer.loadFileContentPatterns(); + } + + @Test + public void shouldReturnAllFilePattern() { + settings.setProperty(IssueExclusionProperties.PATTERNS_ALLFILE_KEY, "1,2"); + settings.setProperty(IssueExclusionProperties.PATTERNS_ALLFILE_KEY + ".1." + IssueExclusionProperties.FILE_REGEXP, "@SONAR-IGNORE-ALL"); + settings.setProperty(IssueExclusionProperties.PATTERNS_ALLFILE_KEY + ".2." + IssueExclusionProperties.FILE_REGEXP, "//FOO-IGNORE-ALL"); + patternsInitializer.loadFileContentPatterns(); + + assertThat(patternsInitializer.hasConfiguredPatterns()).isTrue(); + assertThat(patternsInitializer.hasFileContentPattern()).isTrue(); + assertThat(patternsInitializer.hasMulticriteriaPatterns()).isFalse(); + assertThat(patternsInitializer.getMulticriteriaPatterns().size()).isEqualTo(0); + assertThat(patternsInitializer.getBlockPatterns().size()).isEqualTo(0); + assertThat(patternsInitializer.getAllFilePatterns().size()).isEqualTo(2); + } + + @Test(expected = SonarException.class) + public void shouldLogInvalidAllFilePattern() { + settings.setProperty(IssueExclusionProperties.PATTERNS_ALLFILE_KEY, "1"); + settings.setProperty(IssueExclusionProperties.PATTERNS_ALLFILE_KEY + ".1." + IssueExclusionProperties.FILE_REGEXP, ""); + patternsInitializer.loadFileContentPatterns(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/pattern/IssueInclusionPatternInitializerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/pattern/IssueInclusionPatternInitializerTest.java new file mode 100644 index 00000000000..25c74587522 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/pattern/IssueInclusionPatternInitializerTest.java @@ -0,0 +1,70 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.ignore.pattern; + +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.config.PropertyDefinitions; +import org.sonar.api.config.Settings; +import org.sonar.core.config.IssueExclusionProperties; + +import static org.assertj.core.api.Assertions.assertThat; + +public class IssueInclusionPatternInitializerTest { + + private IssueInclusionPatternInitializer patternsInitializer; + + private Settings settings; + + @Before + public void init() { + settings = new Settings(new PropertyDefinitions(IssueExclusionProperties.all())); + patternsInitializer = new IssueInclusionPatternInitializer(settings); + } + + @Test + public void testNoConfiguration() { + patternsInitializer.initPatterns(); + assertThat(patternsInitializer.hasConfiguredPatterns()).isFalse(); + } + + @Test + public void shouldHavePatternsBasedOnMulticriteriaPattern() { + settings.setProperty("sonar.issue.enforce" + ".multicriteria", "1,2"); + settings.setProperty("sonar.issue.enforce" + ".multicriteria" + ".1." + "resourceKey", "org/foo/Bar.java"); + settings.setProperty("sonar.issue.enforce" + ".multicriteria" + ".1." + "ruleKey", "*"); + settings.setProperty("sonar.issue.enforce" + ".multicriteria" + ".2." + "resourceKey", "org/foo/Hello.java"); + settings.setProperty("sonar.issue.enforce" + ".multicriteria" + ".2." + "ruleKey", "checkstyle:MagicNumber"); + patternsInitializer.initPatterns(); + + assertThat(patternsInitializer.hasConfiguredPatterns()).isTrue(); + assertThat(patternsInitializer.hasMulticriteriaPatterns()).isTrue(); + assertThat(patternsInitializer.getMulticriteriaPatterns().size()).isEqualTo(2); + + patternsInitializer.initializePatternsForPath("org/foo/Bar.java", "org.foo.Bar"); + patternsInitializer.initializePatternsForPath("org/foo/Baz.java", "org.foo.Baz"); + patternsInitializer.initializePatternsForPath("org/foo/Hello.java", "org.foo.Hello"); + + assertThat(patternsInitializer.getPathForComponent("org.foo.Bar")).isEqualTo("org/foo/Bar.java"); + assertThat(patternsInitializer.getPathForComponent("org.foo.Baz")).isEqualTo("org/foo/Baz.java"); + assertThat(patternsInitializer.getPathForComponent("org.foo.Hello")).isEqualTo("org/foo/Hello.java"); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/pattern/IssuePatternTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/pattern/IssuePatternTest.java new file mode 100644 index 00000000000..e67111d0f9c --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/pattern/IssuePatternTest.java @@ -0,0 +1,114 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.ignore.pattern; + +import org.sonar.api.scan.issue.filter.FilterableIssue; + +import org.junit.Test; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rules.Rule; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class IssuePatternTest { + + @Test + public void shouldMatchLines() { + IssuePattern pattern = new IssuePattern("*", "*"); + pattern.addLine(12).addLine(15).addLineRange(20, 25); + + assertThat(pattern.matchLine(3)).isFalse(); + assertThat(pattern.matchLine(12)).isTrue(); + assertThat(pattern.matchLine(14)).isFalse(); + assertThat(pattern.matchLine(21)).isTrue(); + assertThat(pattern.matchLine(6599)).isFalse(); + } + + @Test + public void shouldMatchJavaFile() { + String javaFile = "org.foo.Bar"; + assertThat(new IssuePattern("org.foo.Bar", "*").matchResource(javaFile)).isTrue(); + assertThat(new IssuePattern("org.foo.*", "*").matchResource(javaFile)).isTrue(); + assertThat(new IssuePattern("*Bar", "*").matchResource(javaFile)).isTrue(); + assertThat(new IssuePattern("*", "*").matchResource(javaFile)).isTrue(); + assertThat(new IssuePattern("org.*.?ar", "*").matchResource(javaFile)).isTrue(); + + assertThat(new IssuePattern("org.other.Hello", "*").matchResource(javaFile)).isFalse(); + assertThat(new IssuePattern("org.foo.Hello", "*").matchResource(javaFile)).isFalse(); + assertThat(new IssuePattern("org.*.??ar", "*").matchResource(javaFile)).isFalse(); + assertThat(new IssuePattern("org.*.??ar", "*").matchResource(null)).isFalse(); + assertThat(new IssuePattern("org.*.??ar", "*").matchResource("plop")).isFalse(); + } + + @Test + public void shouldMatchRule() { + RuleKey rule = Rule.create("checkstyle", "IllegalRegexp", "").ruleKey(); + assertThat(new IssuePattern("*", "*").matchRule(rule)).isTrue(); + assertThat(new IssuePattern("*", "checkstyle:*").matchRule(rule)).isTrue(); + assertThat(new IssuePattern("*", "checkstyle:IllegalRegexp").matchRule(rule)).isTrue(); + assertThat(new IssuePattern("*", "checkstyle:Illegal*").matchRule(rule)).isTrue(); + assertThat(new IssuePattern("*", "*:*Illegal*").matchRule(rule)).isTrue(); + + assertThat(new IssuePattern("*", "pmd:IllegalRegexp").matchRule(rule)).isFalse(); + assertThat(new IssuePattern("*", "pmd:*").matchRule(rule)).isFalse(); + assertThat(new IssuePattern("*", "*:Foo*IllegalRegexp").matchRule(rule)).isFalse(); + } + + @Test + public void shouldMatchViolation() { + Rule rule = Rule.create("checkstyle", "IllegalRegexp", ""); + String javaFile = "org.foo.Bar"; + + IssuePattern pattern = new IssuePattern("*", "*"); + pattern.addLine(12); + + assertThat(pattern.match(create(rule, javaFile, null))).isFalse(); + assertThat(pattern.match(create(rule, javaFile, 12))).isTrue(); + assertThat(pattern.match(create((Rule) null, javaFile, 5))).isFalse(); + assertThat(pattern.match(create(rule, null, null))).isFalse(); + assertThat(pattern.match(create((Rule) null, null, null))).isFalse(); + } + + private FilterableIssue create(Rule rule, String component, Integer line) { + FilterableIssue mockIssue = mock(FilterableIssue.class); + RuleKey ruleKey = null; + if (rule != null) { + ruleKey = rule.ruleKey(); + } + when(mockIssue.ruleKey()).thenReturn(ruleKey); + when(mockIssue.componentKey()).thenReturn(component); + when(mockIssue.line()).thenReturn(line); + return mockIssue; + } + + @Test + public void shouldNotMatchNullRule() { + assertThat(new IssuePattern("*", "*").matchRule(null)).isFalse(); + } + + @Test + public void shouldPrintPatternToString() { + IssuePattern pattern = new IssuePattern("*", "checkstyle:*"); + + assertThat(pattern.toString()).isEqualTo( + "IssuePattern[resourcePattern=*,rulePattern=checkstyle:*,lines=[],lineRanges=[],beginBlockRegexp=<null>,endBlockRegexp=<null>,allFileRegexp=<null>,checkLines=true]"); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/pattern/LineRangeTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/pattern/LineRangeTest.java new file mode 100644 index 00000000000..8af95b5d4ad --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/pattern/LineRangeTest.java @@ -0,0 +1,72 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.ignore.pattern; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class LineRangeTest { + + @Test(expected = IllegalArgumentException.class) + public void lineRangeShouldBeOrdered() { + new LineRange(25, 12); + } + + @Test + public void shouldConvertLineRangeToLines() { + LineRange range = new LineRange(12, 15); + + assertThat(range.toLines()).containsOnly(12, 13, 14, 15); + } + + @Test + public void shouldTestInclusionInRangeOfLines() { + LineRange range = new LineRange(12, 15); + + assertThat(range.in(3)).isFalse(); + assertThat(range.in(12)).isTrue(); + assertThat(range.in(13)).isTrue(); + assertThat(range.in(14)).isTrue(); + assertThat(range.in(15)).isTrue(); + assertThat(range.in(16)).isFalse(); + } + + @Test + public void testToString() throws Exception { + assertThat(new LineRange(12, 15).toString()).isEqualTo("[12-15]"); + } + + @Test + public void testEquals() throws Exception { + LineRange range = new LineRange(12, 15); + assertThat(range).isEqualTo(range); + assertThat(range).isEqualTo(new LineRange(12, 15)); + assertThat(range).isNotEqualTo(new LineRange(12, 2000)); + assertThat(range).isNotEqualTo(new LineRange(1000, 2000)); + assertThat(range).isNotEqualTo(null); + assertThat(range).isNotEqualTo(new StringBuffer()); + } + + @Test + public void testHashCode() throws Exception { + assertThat(new LineRange(12, 15).hashCode()).isEqualTo(new LineRange(12, 15).hashCode()); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/pattern/PatternDecoderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/pattern/PatternDecoderTest.java new file mode 100644 index 00000000000..3501d1a2326 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/pattern/PatternDecoderTest.java @@ -0,0 +1,187 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.ignore.pattern; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.utils.SonarException; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PatternDecoderTest { + + private PatternDecoder decoder = new PatternDecoder(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void shouldReadString() { + String patternsList = "# a comment followed by a blank line\n\n" + + "# suppress all violations\n" + + "*;*;*\n\n" + + "# exclude a Java file\n" + + "com.foo.Bar;*;*\n\n" + + "# exclude a Java package\n" + + "com.foo.*;*;*\n\n" + + "# exclude a specific rule\n" + + "*;checkstyle:IllegalRegexp;*\n\n" + + "# exclude a specific rule on a specific file\n" + + "com.foo.Bar;checkstyle:IllegalRegexp;*\n"; + List<IssuePattern> patterns = decoder.decode(patternsList); + + assertThat(patterns).hasSize(5); + } + + @Test + public void shouldCheckFormatOfResource() { + assertThat(PatternDecoder.isResource("")).isFalse(); + assertThat(PatternDecoder.isResource("*")).isTrue(); + assertThat(PatternDecoder.isResource("com.foo.*")).isTrue(); + } + + @Test + public void shouldCheckFormatOfRule() { + assertThat(PatternDecoder.isRule("")).isFalse(); + assertThat(PatternDecoder.isRule("*")).isTrue(); + assertThat(PatternDecoder.isRule("com.foo.*")).isTrue(); + } + + @Test + public void shouldCheckFormatOfLinesRange() { + assertThat(PatternDecoder.isLinesRange("")).isFalse(); + assertThat(PatternDecoder.isLinesRange(" ")).isFalse(); + assertThat(PatternDecoder.isLinesRange("12")).isFalse(); + assertThat(PatternDecoder.isLinesRange("12,212")).isFalse(); + + assertThat(PatternDecoder.isLinesRange("*")).isTrue(); + assertThat(PatternDecoder.isLinesRange("[]")).isTrue(); + assertThat(PatternDecoder.isLinesRange("[13]")).isTrue(); + assertThat(PatternDecoder.isLinesRange("[13,24]")).isTrue(); + assertThat(PatternDecoder.isLinesRange("[13,24,25-500]")).isTrue(); + assertThat(PatternDecoder.isLinesRange("[24-65]")).isTrue(); + assertThat(PatternDecoder.isLinesRange("[13,24-65,84-89,122]")).isTrue(); + } + + @Test + public void shouldReadStarPatterns() { + IssuePattern pattern = decoder.decodeLine("*;*;*"); + + assertThat(pattern.getResourcePattern().toString()).isEqualTo("*"); + assertThat(pattern.getRulePattern().toString()).isEqualTo("*"); + assertThat(pattern.isCheckLines()).isFalse(); + } + + @Test + public void shouldReadLineIds() { + IssuePattern pattern = decoder.decodeLine("*;*;[10,25,98]"); + + assertThat(pattern.isCheckLines()).isTrue(); + assertThat(pattern.getAllLines()).containsOnly(10, 25, 98); + } + + @Test + public void shouldReadRangeOfLineIds() { + IssuePattern pattern = decoder.decodeLine("*;*;[10-12,25,97-100]"); + + assertThat(pattern.isCheckLines()).isTrue(); + assertThat(pattern.getAllLines()).containsOnly(10, 11, 12, 25, 97, 98, 99, 100); + } + + @Test + public void shouldNotExcludeLines() { + // [] is different than * + // - all violations are excluded on * + // * no violations are excluded on [] + IssuePattern pattern = decoder.decodeLine("*;*;[]"); + + assertThat(pattern.isCheckLines()).isTrue(); + assertThat(pattern.getAllLines()).isEmpty(); + } + + @Test + public void shouldReadBlockPattern() { + IssuePattern pattern = decoder.decodeLine("SONAR-OFF;SONAR-ON"); + + assertThat(pattern.getResourcePattern()).isNull(); + assertThat(pattern.getBeginBlockRegexp()).isEqualTo("SONAR-OFF"); + assertThat(pattern.getEndBlockRegexp()).isEqualTo("SONAR-ON"); + } + + @Test + public void shouldReadAllFilePattern() { + IssuePattern pattern = decoder.decodeLine("SONAR-ALL-OFF"); + + assertThat(pattern.getResourcePattern()).isNull(); + assertThat(pattern.getAllFileRegexp()).isEqualTo("SONAR-ALL-OFF"); + } + + @Test + public void shouldFailToReadUncorrectLine1() { + thrown.expect(SonarException.class); + thrown.expectMessage("Exclusions > Issues : Invalid format. The following line has more than 3 fields separated by comma"); + + decoder.decode(";;;;"); + } + + @Test + public void shouldFailToReadUncorrectLine3() { + thrown.expect(SonarException.class); + thrown.expectMessage("Exclusions > Issues : Invalid format. The first field does not define a resource pattern"); + + decoder.decode(";*;*"); + } + + @Test + public void shouldFailToReadUncorrectLine4() { + thrown.expect(SonarException.class); + thrown.expectMessage("Exclusions > Issues : Invalid format. The second field does not define a rule pattern"); + + decoder.decode("*;;*"); + } + + @Test + public void shouldFailToReadUncorrectLine5() { + thrown.expect(SonarException.class); + thrown.expectMessage("Exclusions > Issues : Invalid format. The third field does not define a range of lines"); + + decoder.decode("*;*;blabla"); + } + + @Test + public void shouldFailToReadUncorrectLine6() { + thrown.expect(SonarException.class); + thrown.expectMessage("Exclusions > Issues : Invalid format. The first field does not define a regular expression"); + + decoder.decode(";ON"); + } + + @Test + public void shouldAcceptEmptyEndBlockRegexp() { + IssuePattern pattern = decoder.decodeLine("OFF;"); + + assertThat(pattern.getResourcePattern()).isNull(); + assertThat(pattern.getBeginBlockRegexp()).isEqualTo("OFF"); + assertThat(pattern.getEndBlockRegexp()).isEmpty(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/pattern/PatternMatcherTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/pattern/PatternMatcherTest.java new file mode 100644 index 00000000000..1d69a30c371 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/pattern/PatternMatcherTest.java @@ -0,0 +1,119 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.ignore.pattern; + +import org.sonar.api.scan.issue.filter.FilterableIssue; + +import com.google.common.collect.Sets; +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rules.Rule; + +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class PatternMatcherTest { + + public static final Rule CHECKSTYLE_RULE = Rule.create("checkstyle", "MagicNumber", ""); + public static final String JAVA_FILE = "org.foo.Hello"; + + private PatternMatcher patternMatcher; + + @Before + public void setUp() { + patternMatcher = new PatternMatcher(); + } + + @Test + public void shouldReturnExtraPatternForResource() { + String file = "foo"; + patternMatcher.addPatternToExcludeResource(file); + + IssuePattern extraPattern = patternMatcher.getPatternsForComponent(file).iterator().next(); + assertThat(extraPattern.matchResource(file)).isTrue(); + assertThat(extraPattern.isCheckLines()).isFalse(); + } + + @Test + public void shouldReturnExtraPatternForLinesOfResource() { + String file = "foo"; + Set<LineRange> lineRanges = Sets.newHashSet(); + lineRanges.add(new LineRange(25, 28)); + patternMatcher.addPatternToExcludeLines(file, lineRanges); + + IssuePattern extraPattern = patternMatcher.getPatternsForComponent(file).iterator().next(); + assertThat(extraPattern.matchResource(file)).isTrue(); + assertThat(extraPattern.getAllLines()).isEqualTo(Sets.newHashSet(25, 26, 27, 28)); + } + + @Test + public void shouldHaveNoMatcherIfNoneDefined() { + assertThat(patternMatcher.getMatchingPattern(create(CHECKSTYLE_RULE, JAVA_FILE, null))).isNull(); + } + + @Test + public void shouldMatchWithStandardPatterns() { + patternMatcher.addPatternForComponent(JAVA_FILE, createPattern("org.foo.Hello;checkstyle:MagicNumber;[15-200]")); + + assertThat(patternMatcher.getMatchingPattern(create(CHECKSTYLE_RULE, JAVA_FILE, 150))).isNotNull(); + } + + @Test + public void shouldNotMatchWithStandardPatterns() { + patternMatcher.addPatternForComponent(JAVA_FILE, createPattern("org.foo.Hello;checkstyle:MagicNumber;[15-200]")); + + assertThat(patternMatcher.getMatchingPattern(create(CHECKSTYLE_RULE, JAVA_FILE, 5))).isNull(); + } + + @Test + public void shouldMatchWithExtraPattern() { + patternMatcher.addPatternForComponent(JAVA_FILE, createPattern("org.foo.Hello;*;[15-200]")); + + assertThat(patternMatcher.getMatchingPattern(create(CHECKSTYLE_RULE, JAVA_FILE, 150))).isNotNull(); + } + + @Test + public void shouldNotMatchWithExtraPattern() { + patternMatcher.addPatternForComponent(JAVA_FILE, createPattern("org.foo.Hello;*;[15-200]")); + + assertThat(patternMatcher.getMatchingPattern(create(CHECKSTYLE_RULE, JAVA_FILE, 5))).isNull(); + } + + private FilterableIssue create(Rule rule, String component, Integer line) { + FilterableIssue mockIssue = mock(FilterableIssue.class); + RuleKey ruleKey = null; + if (rule != null) { + ruleKey = rule.ruleKey(); + } + when(mockIssue.ruleKey()).thenReturn(ruleKey); + when(mockIssue.componentKey()).thenReturn(component); + when(mockIssue.line()).thenReturn(line); + return mockIssue; + } + + private IssuePattern createPattern(String line) { + return new PatternDecoder().decode(line).get(0); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/scanner/IssueExclusionsLoaderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/scanner/IssueExclusionsLoaderTest.java new file mode 100644 index 00000000000..52a0e59cbbe --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/scanner/IssueExclusionsLoaderTest.java @@ -0,0 +1,157 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.ignore.scanner; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultFileSystem; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.batch.issue.ignore.pattern.IssueExclusionPatternInitializer; +import org.sonar.batch.issue.ignore.pattern.IssueInclusionPatternInitializer; +import org.sonar.batch.issue.ignore.pattern.PatternMatcher; + +import java.io.File; +import java.io.IOException; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +public class IssueExclusionsLoaderTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Mock + private IssueExclusionsRegexpScanner regexpScanner; + + @Mock + private IssueInclusionPatternInitializer inclusionPatternInitializer; + + @Mock + private IssueExclusionPatternInitializer exclusionPatternInitializer; + + @Mock + private PatternMatcher patternMatcher; + + private DefaultFileSystem fs; + private IssueExclusionsLoader scanner; + private File baseDir; + + @Before + public void before() throws Exception { + baseDir = temp.newFolder(); + fs = new DefaultFileSystem(baseDir.toPath()).setEncoding(UTF_8); + MockitoAnnotations.initMocks(this); + scanner = new IssueExclusionsLoader(regexpScanner, exclusionPatternInitializer, inclusionPatternInitializer, fs); + } + + @Test + public void testToString() throws Exception { + assertThat(scanner.toString()).isEqualTo("Issues Exclusions - Source Scanner"); + } + + @Test + public void shouldExecute() { + when(exclusionPatternInitializer.hasConfiguredPatterns()).thenReturn(true); + when(inclusionPatternInitializer.hasConfiguredPatterns()).thenReturn(true); + assertThat(scanner.shouldExecuteOnProject(null)).isTrue(); + + when(exclusionPatternInitializer.hasConfiguredPatterns()).thenReturn(true); + when(inclusionPatternInitializer.hasConfiguredPatterns()).thenReturn(false); + assertThat(scanner.shouldExecuteOnProject(null)).isTrue(); + + when(exclusionPatternInitializer.hasConfiguredPatterns()).thenReturn(false); + when(inclusionPatternInitializer.hasConfiguredPatterns()).thenReturn(true); + assertThat(scanner.shouldExecuteOnProject(null)).isTrue(); + + when(exclusionPatternInitializer.hasConfiguredPatterns()).thenReturn(false); + when(inclusionPatternInitializer.hasConfiguredPatterns()).thenReturn(false); + assertThat(scanner.shouldExecuteOnProject(null)).isFalse(); + + } + + @Test + public void shouldAnalyzeProject() throws IOException { + File javaFile1 = new File(baseDir, "src/main/java/Foo.java"); + fs.add(new DefaultInputFile("polop", "src/main/java/Foo.java") + .setType(InputFile.Type.MAIN)); + File javaTestFile1 = new File(baseDir, "src/test/java/FooTest.java"); + fs.add(new DefaultInputFile("polop", "src/test/java/FooTest.java") + .setType(InputFile.Type.TEST)); + + when(exclusionPatternInitializer.hasFileContentPattern()).thenReturn(true); + + scanner.execute(); + + verify(inclusionPatternInitializer).initializePatternsForPath("src/main/java/Foo.java", "polop:src/main/java/Foo.java"); + verify(inclusionPatternInitializer).initializePatternsForPath("src/test/java/FooTest.java", "polop:src/test/java/FooTest.java"); + verify(exclusionPatternInitializer).initializePatternsForPath("src/main/java/Foo.java", "polop:src/main/java/Foo.java"); + verify(exclusionPatternInitializer).initializePatternsForPath("src/test/java/FooTest.java", "polop:src/test/java/FooTest.java"); + verify(regexpScanner).scan("polop:src/main/java/Foo.java", javaFile1, UTF_8); + verify(regexpScanner).scan("polop:src/test/java/FooTest.java", javaTestFile1, UTF_8); + } + + @Test + public void shouldAnalyseFilesOnlyWhenRegexConfigured() { + File javaFile1 = new File(baseDir, "src/main/java/Foo.java"); + fs.add(new DefaultInputFile("polop", "src/main/java/Foo.java") + .setType(InputFile.Type.MAIN)); + File javaTestFile1 = new File(baseDir, "src/test/java/FooTest.java"); + fs.add(new DefaultInputFile("polop", "src/test/java/FooTest.java") + .setType(InputFile.Type.TEST)); + when(exclusionPatternInitializer.hasFileContentPattern()).thenReturn(false); + + scanner.execute(); + + verify(inclusionPatternInitializer).initializePatternsForPath("src/main/java/Foo.java", "polop:src/main/java/Foo.java"); + verify(inclusionPatternInitializer).initializePatternsForPath("src/test/java/FooTest.java", "polop:src/test/java/FooTest.java"); + verify(exclusionPatternInitializer).initializePatternsForPath("src/main/java/Foo.java", "polop:src/main/java/Foo.java"); + verify(exclusionPatternInitializer).initializePatternsForPath("src/test/java/FooTest.java", "polop:src/test/java/FooTest.java"); + verifyZeroInteractions(regexpScanner); + } + + @Test + public void shouldReportFailure() throws IOException { + File phpFile1 = new File(baseDir, "src/Foo.php"); + fs.add(new DefaultInputFile("polop", "src/Foo.php") + .setType(InputFile.Type.MAIN)); + + when(exclusionPatternInitializer.hasFileContentPattern()).thenReturn(true); + doThrow(new IOException("BUG")).when(regexpScanner).scan("polop:src/Foo.php", phpFile1, UTF_8); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Unable to read the source file"); + + scanner.execute(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest.java new file mode 100644 index 00000000000..f4aa4ebd9f1 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest.java @@ -0,0 +1,168 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.ignore.scanner; + +import com.google.common.collect.Sets; +import com.google.common.io.Resources; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.sonar.batch.issue.ignore.pattern.IssueExclusionPatternInitializer; +import org.sonar.batch.issue.ignore.pattern.IssuePattern; +import org.sonar.batch.issue.ignore.pattern.LineRange; +import org.sonar.batch.issue.ignore.pattern.PatternMatcher; + +import java.io.File; +import java.util.Arrays; +import java.util.Set; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +public class IssueExclusionsRegexpScannerTest { + + private IssueExclusionsRegexpScanner regexpScanner; + + private String javaFile; + @Mock + private IssueExclusionPatternInitializer patternsInitializer; + @Mock + private PatternMatcher patternMatcher; + @Mock + private IssuePattern allFilePattern; + @Mock + private IssuePattern blockPattern1; + @Mock + private IssuePattern blockPattern2; + + @Before + public void init() { + MockitoAnnotations.initMocks(this); + + when(allFilePattern.getAllFileRegexp()).thenReturn("@SONAR-IGNORE-ALL"); + when(blockPattern1.getBeginBlockRegexp()).thenReturn("// SONAR-OFF"); + when(blockPattern1.getEndBlockRegexp()).thenReturn("// SONAR-ON"); + when(blockPattern2.getBeginBlockRegexp()).thenReturn("// FOO-OFF"); + when(blockPattern2.getEndBlockRegexp()).thenReturn("// FOO-ON"); + when(patternsInitializer.getAllFilePatterns()).thenReturn(Arrays.asList(allFilePattern)); + when(patternsInitializer.getBlockPatterns()).thenReturn(Arrays.asList(blockPattern1, blockPattern2)); + when(patternsInitializer.getPatternMatcher()).thenReturn(patternMatcher); + + regexpScanner = new IssueExclusionsRegexpScanner(patternsInitializer); + verify(patternsInitializer, times(1)).getAllFilePatterns(); + verify(patternsInitializer, times(1)).getBlockPatterns(); + + javaFile = "org.sonar.test.MyFile"; + } + + @Test + public void shouldDoNothing() throws Exception { + regexpScanner.scan(javaFile, new File(Resources.getResource( + "org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-no-regexp.txt").toURI()), UTF_8); + + verifyNoMoreInteractions(patternsInitializer); + } + + @Test + public void shouldAddPatternToExcludeFile() throws Exception { + regexpScanner.scan(javaFile, new File(Resources.getResource( + "org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-single-regexp.txt").toURI()), UTF_8); + + verify(patternsInitializer).getPatternMatcher(); + verify(patternMatcher, times(1)).addPatternToExcludeResource(javaFile); + verifyNoMoreInteractions(patternsInitializer); + } + + @Test + public void shouldAddPatternToExcludeFileEvenIfAlsoDoubleRegexps() throws Exception { + regexpScanner.scan(javaFile, new File(Resources.getResource( + "org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-single-regexp-and-double-regexp.txt").toURI()), UTF_8); + + verify(patternsInitializer).getPatternMatcher(); + verify(patternMatcher, times(1)).addPatternToExcludeResource(javaFile); + verifyNoMoreInteractions(patternsInitializer); + } + + @Test + public void shouldAddPatternToExcludeLines() throws Exception { + regexpScanner.scan(javaFile, new File(Resources.getResource( + "org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-double-regexp.txt").toURI()), UTF_8); + + Set<LineRange> lineRanges = Sets.newHashSet(); + lineRanges.add(new LineRange(21, 25)); + verify(patternsInitializer).getPatternMatcher(); + verify(patternMatcher, times(1)).addPatternToExcludeLines(javaFile, lineRanges); + verifyNoMoreInteractions(patternsInitializer); + } + + @Test + public void shouldAddPatternToExcludeLinesTillTheEnd() throws Exception { + regexpScanner.scan(javaFile, new File(Resources.getResource( + "org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-double-regexp-unfinished.txt").toURI()), UTF_8); + + Set<LineRange> lineRanges = Sets.newHashSet(); + lineRanges.add(new LineRange(21, 34)); + verify(patternsInitializer).getPatternMatcher(); + verify(patternMatcher, times(1)).addPatternToExcludeLines(javaFile, lineRanges); + verifyNoMoreInteractions(patternsInitializer); + } + + @Test + public void shouldAddPatternToExcludeSeveralLineRanges() throws Exception { + regexpScanner.scan(javaFile, new File(Resources.getResource( + "org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-double-regexp-twice.txt").toURI()), UTF_8); + + Set<LineRange> lineRanges = Sets.newHashSet(); + lineRanges.add(new LineRange(21, 25)); + lineRanges.add(new LineRange(29, 33)); + verify(patternsInitializer).getPatternMatcher(); + verify(patternMatcher, times(1)).addPatternToExcludeLines(javaFile, lineRanges); + verifyNoMoreInteractions(patternsInitializer); + } + + @Test + public void shouldAddPatternToExcludeLinesWithWrongOrder() throws Exception { + regexpScanner.scan(javaFile, new File(Resources.getResource( + "org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-double-regexp-wrong-order.txt").toURI()), UTF_8); + + Set<LineRange> lineRanges = Sets.newHashSet(); + lineRanges.add(new LineRange(25, 35)); + verify(patternsInitializer).getPatternMatcher(); + verify(patternMatcher, times(1)).addPatternToExcludeLines(javaFile, lineRanges); + verifyNoMoreInteractions(patternsInitializer); + } + + @Test + public void shouldAddPatternToExcludeLinesWithMess() throws Exception { + regexpScanner.scan(javaFile, new File(Resources.getResource( + "org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-double-regexp-mess.txt").toURI()), UTF_8); + + Set<LineRange> lineRanges = Sets.newHashSet(); + lineRanges.add(new LineRange(21, 29)); + verify(patternsInitializer).getPatternMatcher(); + verify(patternMatcher, times(1)).addPatternToExcludeLines(javaFile, lineRanges); + verifyNoMoreInteractions(patternsInitializer); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/tracking/DefaultServerLineHashesLoaderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/tracking/DefaultServerLineHashesLoaderTest.java new file mode 100644 index 00000000000..7d8ab1d2319 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/tracking/DefaultServerLineHashesLoaderTest.java @@ -0,0 +1,89 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.tracking; + +import org.sonar.batch.cache.WSLoader.LoadStrategy; +import org.sonar.batch.cache.WSLoaderResult; +import org.sonar.batch.cache.WSLoader; +import org.apache.commons.lang.mutable.MutableBoolean; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.utils.HttpDownloader; + +import java.net.URI; +import java.net.URISyntaxException; + +import static org.mockito.Matchers.any; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class DefaultServerLineHashesLoaderTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Before + public void before() { + } + + @Test + public void should_download_source_from_ws_if_preview_mode() { + WSLoader wsLoader = mock(WSLoader.class); + when(wsLoader.loadString(anyString(), any(LoadStrategy.class))).thenReturn(new WSLoaderResult<>("ae12\n\n43fb", true)); + + ServerLineHashesLoader lastSnapshots = new DefaultServerLineHashesLoader(wsLoader); + + String[] hashes = lastSnapshots.getLineHashes("myproject:org/foo/Bar.c", null); + assertThat(hashes).containsOnly("ae12", "", "43fb"); + verify(wsLoader).loadString("/api/sources/hash?key=myproject%3Aorg%2Ffoo%2FBar.c", LoadStrategy.CACHE_FIRST); + } + + @Test + public void should_download_source_with_space_from_ws_if_preview_mode() { + WSLoader server = mock(WSLoader.class); + when(server.loadString(anyString(), any(LoadStrategy.class))).thenReturn(new WSLoaderResult<>("ae12\n\n43fb", true)); + + ServerLineHashesLoader lastSnapshots = new DefaultServerLineHashesLoader(server); + + MutableBoolean fromCache = new MutableBoolean(); + String[] hashes = lastSnapshots.getLineHashes("myproject:org/foo/Foo Bar.c", fromCache); + assertThat(fromCache.booleanValue()).isTrue(); + assertThat(hashes).containsOnly("ae12", "", "43fb"); + verify(server).loadString("/api/sources/hash?key=myproject%3Aorg%2Ffoo%2FFoo+Bar.c", LoadStrategy.CACHE_FIRST); + } + + @Test + public void should_fail_to_download_source_from_ws() throws URISyntaxException { + WSLoader server = mock(WSLoader.class); + when(server.loadString(anyString(), any(LoadStrategy.class))).thenThrow(new HttpDownloader.HttpException(new URI(""), 500)); + + ServerLineHashesLoader lastSnapshots = new DefaultServerLineHashesLoader(server); + + thrown.expect(HttpDownloader.HttpException.class); + lastSnapshots.getLineHashes("foo", null); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/tracking/RollingFileHashesTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/tracking/RollingFileHashesTest.java new file mode 100644 index 00000000000..8648fba48d1 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/tracking/RollingFileHashesTest.java @@ -0,0 +1,43 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.tracking; + +import org.junit.Test; + +import static org.apache.commons.codec.digest.DigestUtils.md5Hex; +import static org.assertj.core.api.Assertions.assertThat; + +public class RollingFileHashesTest { + + @Test + public void test_equals() { + RollingFileHashes a = RollingFileHashes.create(FileHashes.create(new String[] {md5Hex("line0"), md5Hex("line1"), md5Hex("line2")}), 1); + RollingFileHashes b = RollingFileHashes.create(FileHashes.create(new String[] {md5Hex("line0"), md5Hex("line1"), md5Hex("line2"), md5Hex("line3")}), 1); + + assertThat(a.getHash(1) == b.getHash(1)).isTrue(); + assertThat(a.getHash(2) == b.getHash(2)).isTrue(); + assertThat(a.getHash(3) == b.getHash(3)).isFalse(); + + RollingFileHashes c = RollingFileHashes.create(FileHashes.create(new String[] {md5Hex("line-1"), md5Hex("line0"), md5Hex("line1"), md5Hex("line2"), md5Hex("line3")}), 1); + assertThat(a.getHash(1) == c.getHash(2)).isFalse(); + assertThat(a.getHash(2) == c.getHash(3)).isTrue(); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/tracking/SourceHashHolderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/tracking/SourceHashHolderTest.java new file mode 100644 index 00000000000..9acf02b3577 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/tracking/SourceHashHolderTest.java @@ -0,0 +1,119 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.tracking; + +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.mockito.Mockito; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; + +import java.io.File; +import java.nio.charset.StandardCharsets; + +import static org.apache.commons.codec.digest.DigestUtils.md5Hex; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class SourceHashHolderTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + SourceHashHolder sourceHashHolder; + + ServerLineHashesLoader lastSnapshots; + DefaultInputFile file; + + private File ioFile; + + @Before + public void setUp() throws Exception { + lastSnapshots = mock(ServerLineHashesLoader.class); + file = mock(DefaultInputFile.class); + ioFile = temp.newFile(); + when(file.file()).thenReturn(ioFile); + when(file.path()).thenReturn(ioFile.toPath()); + when(file.lines()).thenReturn(1); + when(file.charset()).thenReturn(StandardCharsets.UTF_8); + + sourceHashHolder = new SourceHashHolder(file, lastSnapshots); + } + + @Test + public void should_lazy_load_line_hashes() throws Exception { + final String source = "source"; + FileUtils.write(ioFile, source + "\n", StandardCharsets.UTF_8); + when(file.lines()).thenReturn(2); + + assertThat(sourceHashHolder.getHashedSource().getHash(1)).isEqualTo(md5Hex(source)); + assertThat(sourceHashHolder.getHashedSource().getHash(2)).isEqualTo(""); + verify(file).key(); + verify(file).status(); + + assertThat(sourceHashHolder.getHashedSource().getHash(1)).isEqualTo(md5Hex(source)); + } + + @Test + public void should_lazy_load_reference_hashes_when_status_changed() throws Exception { + final String source = "source"; + String key = "foo:src/Foo.java"; + FileUtils.write(ioFile, source, StandardCharsets.UTF_8); + when(file.key()).thenReturn(key); + when(file.status()).thenReturn(InputFile.Status.CHANGED); + when(lastSnapshots.getLineHashes(key, null)).thenReturn(new String[] {md5Hex(source)}); + + assertThat(sourceHashHolder.getHashedReference().getHash(1)).isEqualTo(md5Hex(source)); + verify(lastSnapshots).getLineHashes(key, null); + + assertThat(sourceHashHolder.getHashedReference().getHash(1)).isEqualTo(md5Hex(source)); + Mockito.verifyNoMoreInteractions(lastSnapshots); + } + + @Test + public void should_not_load_reference_hashes_when_status_same() throws Exception { + final String source = "source"; + String key = "foo:src/Foo.java"; + FileUtils.write(ioFile, source, StandardCharsets.UTF_8); + when(file.key()).thenReturn(key); + when(file.status()).thenReturn(InputFile.Status.SAME); + + assertThat(sourceHashHolder.getHashedReference().getHash(1)).isEqualTo(md5Hex(source)); + Mockito.verifyNoMoreInteractions(lastSnapshots); + } + + @Test + public void no_reference_hashes_when_status_added() throws Exception { + final String source = "source"; + String key = "foo:src/Foo.java"; + FileUtils.write(ioFile, source, StandardCharsets.UTF_8); + when(file.key()).thenReturn(key); + when(file.status()).thenReturn(InputFile.Status.ADDED); + + assertThat(sourceHashHolder.getHashedReference()).isNull(); + Mockito.verifyNoMoreInteractions(lastSnapshots); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/tracking/TrackedIssueTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/tracking/TrackedIssueTest.java new file mode 100644 index 00000000000..4228f912b94 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/issue/tracking/TrackedIssueTest.java @@ -0,0 +1,47 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.issue.tracking; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; + +public class TrackedIssueTest { + @Test + public void round_trip() { + TrackedIssue issue = new TrackedIssue(); + issue.setStartLine(15); + + assertThat(issue.getLine()).isEqualTo(15); + assertThat(issue.startLine()).isEqualTo(15); + } + + @Test + public void hashes() { + String[] hashArr = new String[] { + "hash1", "hash2", "hash3" + }; + + FileHashes hashes = FileHashes.create(hashArr); + TrackedIssue issue = new TrackedIssue(hashes); + issue.setStartLine(1); + assertThat(issue.getLineHash()).isEqualTo("hash1"); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/BatchMediumTester.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/BatchMediumTester.java new file mode 100644 index 00000000000..619977ce156 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/BatchMediumTester.java @@ -0,0 +1,497 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest; + +import org.sonar.api.rule.RuleKey; + +import org.sonar.batch.rule.LoadedActiveRule; +import org.sonar.batch.repository.FileData; +import org.sonar.api.utils.DateUtils; +import com.google.common.collect.Table; +import com.google.common.collect.HashBasedTable; +import org.sonar.batch.repository.ProjectRepositories; +import org.sonar.batch.rule.ActiveRulesLoader; +import org.sonarqube.ws.QualityProfiles.SearchWsResponse.QualityProfile; +import org.sonar.batch.repository.QualityProfileLoader; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.mutable.MutableBoolean; + +import javax.annotation.Nullable; + +import org.sonarqube.ws.Rules.ListResponse.Rule; +import org.sonar.batch.bootstrapper.IssueListener; +import org.sonar.api.server.rule.RulesDefinition.Repository; +import org.sonar.api.server.rule.RulesDefinition; +import org.sonar.batch.rule.RulesLoader; +import org.sonar.scanner.protocol.input.GlobalRepositories; +import org.sonar.scanner.protocol.input.ScannerInput.ServerIssue; +import com.google.common.base.Function; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.sonar.api.CoreProperties; +import org.sonar.api.SonarPlugin; +import org.sonar.api.batch.debt.internal.DefaultDebtModel; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Metric; +import org.sonar.batch.bootstrapper.Batch; +import org.sonar.batch.bootstrapper.EnvironmentInformation; +import org.sonar.batch.bootstrapper.LogOutput; +import org.sonar.batch.issue.tracking.ServerLineHashesLoader; +import org.sonar.batch.report.ReportPublisher; +import org.sonar.batch.repository.GlobalRepositoriesLoader; +import org.sonar.batch.repository.ProjectRepositoriesLoader; +import org.sonar.batch.repository.ServerIssuesLoader; + +/** + * Main utility class for writing batch medium tests. + * + */ +public class BatchMediumTester { + + public static final String MEDIUM_TEST_ENABLED = "sonar.mediumTest.enabled"; + private Batch batch; + private static Path workingDir = null; + private static Path globalWorkingDir = null; + + private static void createWorkingDirs() throws IOException { + destroyWorkingDirs(); + + workingDir = java.nio.file.Files.createTempDirectory("mediumtest-working-dir"); + globalWorkingDir = java.nio.file.Files.createTempDirectory("mediumtest-global-working-dir"); + } + + private static void destroyWorkingDirs() throws IOException { + if (workingDir != null) { + FileUtils.deleteDirectory(workingDir.toFile()); + workingDir = null; + } + + if (globalWorkingDir != null) { + FileUtils.deleteDirectory(globalWorkingDir.toFile()); + globalWorkingDir = null; + } + + } + + public static BatchMediumTesterBuilder builder() { + try { + createWorkingDirs(); + } catch (IOException e) { + e.printStackTrace(); + } + + BatchMediumTesterBuilder builder = new BatchMediumTesterBuilder().registerCoreMetrics(); + builder.bootstrapProperties.put(MEDIUM_TEST_ENABLED, "true"); + builder.bootstrapProperties.put(ReportPublisher.KEEP_REPORT_PROP_KEY, "true"); + builder.bootstrapProperties.put(CoreProperties.WORKING_DIRECTORY, workingDir.toString()); + builder.bootstrapProperties.put("sonar.userHome", globalWorkingDir.toString()); + return builder; + } + + public static class BatchMediumTesterBuilder { + private final FakeGlobalRepositoriesLoader globalRefProvider = new FakeGlobalRepositoriesLoader(); + private final FakeProjectRepositoriesLoader projectRefProvider = new FakeProjectRepositoriesLoader(); + private final FakePluginInstaller pluginInstaller = new FakePluginInstaller(); + private final FakeServerIssuesLoader serverIssues = new FakeServerIssuesLoader(); + private final FakeServerLineHashesLoader serverLineHashes = new FakeServerLineHashesLoader(); + private final Map<String, String> bootstrapProperties = new HashMap<>(); + private final FakeRulesLoader rulesLoader = new FakeRulesLoader(); + private final FakeQualityProfileLoader qualityProfiles = new FakeQualityProfileLoader(); + private final FakeActiveRulesLoader activeRules = new FakeActiveRulesLoader(); + private boolean associated = true; + private LogOutput logOutput = null; + + public BatchMediumTester build() { + return new BatchMediumTester(this); + } + + public BatchMediumTesterBuilder setAssociated(boolean associated) { + this.associated = associated; + return this; + } + + public BatchMediumTesterBuilder setLogOutput(LogOutput logOutput) { + this.logOutput = logOutput; + return this; + } + + public BatchMediumTesterBuilder registerPlugin(String pluginKey, File location) { + pluginInstaller.add(pluginKey, location); + return this; + } + + public BatchMediumTesterBuilder registerPlugin(String pluginKey, SonarPlugin instance) { + pluginInstaller.add(pluginKey, instance); + return this; + } + + public BatchMediumTesterBuilder registerCoreMetrics() { + for (Metric<?> m : CoreMetrics.getMetrics()) { + registerMetric(m); + } + return this; + } + + public BatchMediumTesterBuilder registerMetric(Metric<?> metric) { + globalRefProvider.add(metric); + return this; + } + + public BatchMediumTesterBuilder addQProfile(String language, String name) { + qualityProfiles.add(language, name); + return this; + } + + public BatchMediumTesterBuilder addRule(Rule rule) { + rulesLoader.addRule(rule); + return this; + } + + public BatchMediumTesterBuilder addRule(String key, String repoKey, String internalKey, String name) { + Rule.Builder builder = Rule.newBuilder(); + builder.setKey(key); + builder.setRepository(repoKey); + if (internalKey != null) { + builder.setInternalKey(internalKey); + } + builder.setName(name); + + rulesLoader.addRule(builder.build()); + return this; + } + + public BatchMediumTesterBuilder addRules(RulesDefinition rulesDefinition) { + RulesDefinition.Context context = new RulesDefinition.Context(); + rulesDefinition.define(context); + List<Repository> repositories = context.repositories(); + for (Repository repo : repositories) { + for (RulesDefinition.Rule rule : repo.rules()) { + this.addRule(rule.key(), rule.repository().key(), rule.internalKey(), rule.name()); + } + } + return this; + } + + public BatchMediumTesterBuilder addDefaultQProfile(String language, String name) { + addQProfile(language, name); + globalRefProvider.globalSettings().put("sonar.profile." + language, name); + return this; + } + + public BatchMediumTesterBuilder setPreviousAnalysisDate(Date previousAnalysis) { + projectRefProvider.setLastAnalysisDate(previousAnalysis); + return this; + } + + public BatchMediumTesterBuilder bootstrapProperties(Map<String, String> props) { + bootstrapProperties.putAll(props); + return this; + } + + public BatchMediumTesterBuilder activateRule(LoadedActiveRule activeRule) { + activeRules.addActiveRule(activeRule); + return this; + } + + public BatchMediumTesterBuilder addActiveRule(String repositoryKey, String ruleKey, @Nullable String templateRuleKey, String name, @Nullable String severity, + @Nullable String internalKey, @Nullable String languag) { + LoadedActiveRule r = new LoadedActiveRule(); + + r.setInternalKey(internalKey); + r.setRuleKey(RuleKey.of(repositoryKey, ruleKey)); + r.setName(name); + r.setTemplateRuleKey(templateRuleKey); + r.setLanguage(languag); + r.setSeverity(severity); + + activeRules.addActiveRule(r); + return this; + } + + public BatchMediumTesterBuilder addFileData(String moduleKey, String path, FileData fileData) { + projectRefProvider.addFileData(moduleKey, path, fileData); + return this; + } + + public BatchMediumTesterBuilder setLastBuildDate(Date d) { + projectRefProvider.setLastAnalysisDate(d); + return this; + } + + public BatchMediumTesterBuilder mockServerIssue(ServerIssue issue) { + serverIssues.getServerIssues().add(issue); + return this; + } + + public BatchMediumTesterBuilder mockLineHashes(String fileKey, String[] lineHashes) { + serverLineHashes.byKey.put(fileKey, lineHashes); + return this; + } + + } + + public void start() { + batch.start(); + } + + public void stop() { + batch.stop(); + try { + destroyWorkingDirs(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void syncProject(String projectKey) { + batch.syncProject(projectKey); + } + + private BatchMediumTester(BatchMediumTesterBuilder builder) { + Batch.Builder batchBuilder = Batch.builder() + .setEnableLoggingConfiguration(true) + .addComponents( + new EnvironmentInformation("mediumTest", "1.0"), + builder.pluginInstaller, + builder.globalRefProvider, + builder.qualityProfiles, + builder.rulesLoader, + builder.projectRefProvider, + builder.activeRules, + new DefaultDebtModel()) + .setBootstrapProperties(builder.bootstrapProperties) + .setLogOutput(builder.logOutput); + + if (builder.associated) { + batchBuilder.addComponents( + builder.serverIssues); + } + batch = batchBuilder.build(); + } + + public TaskBuilder newTask() { + return new TaskBuilder(this); + } + + public TaskBuilder newScanTask(File sonarProps) { + Properties prop = new Properties(); + try (Reader reader = new InputStreamReader(new FileInputStream(sonarProps), StandardCharsets.UTF_8)) { + prop.load(reader); + } catch (Exception e) { + throw new IllegalStateException("Unable to read configuration file", e); + } + TaskBuilder builder = new TaskBuilder(this); + builder.property("sonar.projectBaseDir", sonarProps.getParentFile().getAbsolutePath()); + for (Map.Entry<Object, Object> entry : prop.entrySet()) { + builder.property(entry.getKey().toString(), entry.getValue().toString()); + } + return builder; + } + + public static class TaskBuilder { + private final Map<String, String> taskProperties = new HashMap<>(); + private BatchMediumTester tester; + private IssueListener issueListener = null; + + public TaskBuilder(BatchMediumTester tester) { + this.tester = tester; + } + + public TaskResult start() { + TaskResult result = new TaskResult(); + Map<String, String> props = new HashMap<>(); + props.putAll(taskProperties); + if (issueListener != null) { + tester.batch.executeTask(props, result, issueListener); + } else { + tester.batch.executeTask(props, result); + } + return result; + } + + public TaskBuilder properties(Map<String, String> props) { + taskProperties.putAll(props); + return this; + } + + public TaskBuilder property(String key, String value) { + taskProperties.put(key, value); + return this; + } + + public TaskBuilder setIssueListener(IssueListener issueListener) { + this.issueListener = issueListener; + return this; + } + } + + private static class FakeRulesLoader implements RulesLoader { + private List<org.sonarqube.ws.Rules.ListResponse.Rule> rules = new LinkedList<>(); + + public FakeRulesLoader addRule(Rule rule) { + rules.add(rule); + return this; + } + + @Override + public List<Rule> load(@Nullable MutableBoolean fromCache) { + return rules; + } + } + + private static class FakeActiveRulesLoader implements ActiveRulesLoader { + private List<LoadedActiveRule> activeRules = new LinkedList<>(); + + public void addActiveRule(LoadedActiveRule activeRule) { + this.activeRules.add(activeRule); + } + + @Override + public List<LoadedActiveRule> load(String qualityProfileKey, MutableBoolean fromCache) { + return activeRules; + } + } + + private static class FakeGlobalRepositoriesLoader implements GlobalRepositoriesLoader { + + private int metricId = 1; + + private GlobalRepositories ref = new GlobalRepositories(); + + @Override + public GlobalRepositories load(@Nullable MutableBoolean fromCache) { + return ref; + } + + public Map<String, String> globalSettings() { + return ref.globalSettings(); + } + + public FakeGlobalRepositoriesLoader add(Metric<?> metric) { + Boolean optimizedBestValue = metric.isOptimizedBestValue(); + ref.metrics().add(new org.sonar.scanner.protocol.input.Metric(metricId, + metric.key(), + metric.getType().name(), + metric.getDescription(), + metric.getDirection(), + metric.getName(), + metric.getQualitative(), + metric.getUserManaged(), + metric.getWorstValue(), + metric.getBestValue(), + optimizedBestValue != null ? optimizedBestValue : false)); + metricId++; + return this; + } + + } + + private static class FakeProjectRepositoriesLoader implements ProjectRepositoriesLoader { + + private Table<String, String, FileData> fileDataTable = HashBasedTable.create(); + private Date lastAnalysisDate; + + @Override + public ProjectRepositories load(String projectKey, boolean isIssuesMode, @Nullable MutableBoolean fromCache) { + Table<String, String, String> settings = HashBasedTable.create(); + return new ProjectRepositories(settings, fileDataTable, lastAnalysisDate); + } + + public FakeProjectRepositoriesLoader addFileData(String moduleKey, String path, FileData fileData) { + fileDataTable.put(moduleKey, path, fileData); + return this; + } + + public FakeProjectRepositoriesLoader setLastAnalysisDate(Date d) { + lastAnalysisDate = d; + return this; + } + + } + + private static class FakeQualityProfileLoader implements QualityProfileLoader { + + private List<QualityProfile> qualityProfiles = new LinkedList<>(); + + public void add(String language, String name) { + qualityProfiles.add(QualityProfile.newBuilder() + .setLanguage(language) + .setKey(name) + .setName(name) + .setRulesUpdatedAt(DateUtils.formatDateTime(new Date(1234567891212L))) + .build()); + } + + @Override + public List<QualityProfile> load(String projectKey, String profileName, MutableBoolean fromCache) { + return qualityProfiles; + } + + @Override + public List<QualityProfile> loadDefault(String profileName, MutableBoolean fromCache) { + return qualityProfiles; + } + } + + private static class FakeServerIssuesLoader implements ServerIssuesLoader { + + private List<ServerIssue> serverIssues = new ArrayList<>(); + + public List<ServerIssue> getServerIssues() { + return serverIssues; + } + + @Override + public boolean load(String componentKey, Function<ServerIssue, Void> consumer) { + for (ServerIssue serverIssue : serverIssues) { + consumer.apply(serverIssue); + } + return true; + } + } + + private static class FakeServerLineHashesLoader implements ServerLineHashesLoader { + private Map<String, String[]> byKey = new HashMap<>(); + + @Override + public String[] getLineHashes(String fileKey, @Nullable MutableBoolean fromCache) { + if (byKey.containsKey(fileKey)) { + return byKey.get(fileKey); + } else { + throw new IllegalStateException("You forgot to mock line hashes for " + fileKey); + } + } + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/LogOutputRecorder.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/LogOutputRecorder.java new file mode 100644 index 00000000000..edae5e47fb1 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/LogOutputRecorder.java @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import com.google.common.collect.Multimap; +import com.google.common.collect.HashMultimap; +import org.sonar.batch.bootstrapper.LogOutput; + +public class LogOutputRecorder implements LogOutput { + private Multimap<String, String> recordedByLevel = HashMultimap.create(); + private List<String> recorded = new LinkedList<>(); + private StringBuffer asString = new StringBuffer(); + + @Override + public void log(String formattedMessage, Level level) { + recordedByLevel.put(level.toString(), formattedMessage); + recorded.add(formattedMessage); + asString.append(formattedMessage).append("\n"); + } + + public Collection<String> getAll() { + return recorded; + } + + public Collection<String> get(String level) { + return recordedByLevel.get(level); + } + + public String getAsString() { + return asString.toString(); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/cache/CacheSyncTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/cache/CacheSyncTest.java new file mode 100644 index 00000000000..4ebf5eb1fa6 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/cache/CacheSyncTest.java @@ -0,0 +1,155 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.cache; + +import org.junit.rules.TemporaryFolder; + +import org.sonar.batch.mediumtest.TaskResult; +import org.sonar.batch.mediumtest.BatchMediumTester.TaskBuilder; +import org.sonar.batch.mediumtest.LogOutputRecorder; +import org.sonar.batch.repository.FileData; +import com.google.common.collect.ImmutableMap; + +import java.util.Date; + +import static org.assertj.core.api.Assertions.assertThat; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.CoreProperties; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.xoo.XooPlugin; +import org.sonar.xoo.rule.XooRulesDefinition; + +public class CacheSyncTest { + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private BatchMediumTester tester; + + @After + public void stop() { + if (tester != null) { + tester.stop(); + tester = null; + } + } + + @Test + public void testExecuteTask() { + LogOutputRecorder logOutput = new LogOutputRecorder(); + + tester = BatchMediumTester.builder() + .bootstrapProperties(ImmutableMap.of(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_ISSUES, + "sonar.verbose", "true")) + .registerPlugin("xoo", new XooPlugin()) + .addRules(new XooRulesDefinition()) + .addQProfile("lang", "name") + .addActiveRule("xoo", "OneIssuePerLine", null, "One issue per line", "MAJOR", "my/internal/key", "xoo") + .setPreviousAnalysisDate(new Date()) + .addFileData("test-project", "file1", new FileData("hash", "123456789")) + .setLogOutput(logOutput) + .build(); + + tester.start(); + executeTask(tester.newTask()); + assertThat(logOutput.getAsString()).contains("Cache for project [key] not found, synchronizing"); + } + + @Test + public void testSyncFirstTime() { + LogOutputRecorder logOutput = new LogOutputRecorder(); + + tester = BatchMediumTester.builder() + .bootstrapProperties(ImmutableMap.of(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_ISSUES, + "sonar.verbose", "true")) + .registerPlugin("xoo", new XooPlugin()) + .addRules(new XooRulesDefinition()) + .addQProfile("lang", "name") + .addActiveRule("xoo", "OneIssuePerLine", null, "One issue per line", "MAJOR", "my/internal/key", "xoo") + .setPreviousAnalysisDate(new Date()) + .addFileData("test-project", "file1", new FileData("hash", "123456789")) + .setLogOutput(logOutput) + .build(); + + tester.start(); + tester.syncProject("test-project"); + assertThat(logOutput.getAsString()).contains("Cache for project [test-project] not found"); + } + + @Test + public void testSyncTwice() { + LogOutputRecorder logOutput = new LogOutputRecorder(); + + tester = BatchMediumTester.builder() + .bootstrapProperties(ImmutableMap.of(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_ISSUES, + "sonar.verbose", "true")) + .registerPlugin("xoo", new XooPlugin()) + .addRules(new XooRulesDefinition()) + .addQProfile("lang", "name") + .addActiveRule("xoo", "OneIssuePerLine", null, "One issue per line", "MAJOR", "my/internal/key", "xoo") + .setPreviousAnalysisDate(new Date()) + .addFileData("test-project", "file1", new FileData("hash", "123456789")) + .setLogOutput(logOutput) + .build(); + + tester.start(); + tester.syncProject("test-project"); + tester.syncProject("test-project"); + assertThat(logOutput.getAsString()).contains("-- Found project [test-project]"); + assertThat(logOutput.getAsString()).contains("not found, synchronizing data"); + assertThat(logOutput.getAsString()).contains("], synchronizing data (forced).."); + } + + @Test + public void testNonAssociated() { + LogOutputRecorder logOutput = new LogOutputRecorder(); + + tester = BatchMediumTester.builder() + .bootstrapProperties(ImmutableMap.of(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_ISSUES)) + .registerPlugin("xoo", new XooPlugin()) + .addRules(new XooRulesDefinition()) + .addQProfile("lang", "name") + .addActiveRule("xoo", "OneIssuePerLine", null, "One issue per line", "MAJOR", "my/internal/key", "xoo") + .setPreviousAnalysisDate(new Date()) + .addFileData("test-project", "file1", new FileData("hash", "123456789")) + .setLogOutput(logOutput) + .build(); + + tester.start(); + tester.syncProject(null); + + assertThat(logOutput.getAsString()).contains("Cache not found, synchronizing data"); + } + + private TaskResult executeTask(TaskBuilder builder) { + builder.property("sonar.projectKey", "key"); + builder.property("sonar.projectVersion", "1.0"); + builder.property("sonar.projectName", "key"); + builder.property("sonar.projectBaseDir", temp.getRoot().getAbsolutePath()); + builder.property("sonar.sources", temp.getRoot().getAbsolutePath()); + return builder.start(); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/coverage/CoverageMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/coverage/CoverageMediumTest.java new file mode 100644 index 00000000000..e3dac6ed869 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/coverage/CoverageMediumTest.java @@ -0,0 +1,180 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.coverage; + +import com.google.common.collect.ImmutableMap; +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.TaskResult; +import org.sonar.xoo.XooPlugin; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; + +public class CoverageMediumTest { + + @org.junit.Rule + public TemporaryFolder temp = new TemporaryFolder(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .registerPlugin("xoo", new XooPlugin()) + .addDefaultQProfile("xoo", "Sonar Way") + .build(); + + @Before + public void prepare() { + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + + @Test + public void unitTests() throws IOException { + + File baseDir = temp.getRoot(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + File xooUtCoverageFile = new File(srcDir, "sample.xoo.coverage"); + FileUtils.write(xooFile, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}"); + FileUtils.write(xooUtCoverageFile, "2:2:2:1\n3:1"); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .build()) + .start(); + + InputFile file = result.inputFile("src/sample.xoo"); + assertThat(result.coverageFor(file, 2).getUtHits()).isTrue(); + assertThat(result.coverageFor(file, 2).getItHits()).isFalse(); + assertThat(result.coverageFor(file, 2).getConditions()).isEqualTo(2); + assertThat(result.coverageFor(file, 2).getUtCoveredConditions()).isEqualTo(1); + assertThat(result.coverageFor(file, 2).getItCoveredConditions()).isEqualTo(0); + assertThat(result.coverageFor(file, 2).getOverallCoveredConditions()).isEqualTo(0); + + Map<String, List<org.sonar.scanner.protocol.output.ScannerReport.Measure>> allMeasures = result.allMeasures(); + assertThat(allMeasures.get("com.foo.project:src/sample.xoo")).extracting("metricKey", "intValue") + .contains(tuple(CoreMetrics.LINES_TO_COVER_KEY, 2), + tuple(CoreMetrics.UNCOVERED_LINES_KEY, 0), + tuple(CoreMetrics.CONDITIONS_TO_COVER_KEY, 2), + tuple(CoreMetrics.UNCOVERED_CONDITIONS_KEY, 1)); + } + + @Test + public void exclusions() throws IOException { + + File baseDir = temp.getRoot(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + File xooUtCoverageFile = new File(srcDir, "sample.xoo.coverage"); + FileUtils.write(xooFile, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}"); + FileUtils.write(xooUtCoverageFile, "2:2:2:1\n3:1"); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .put("sonar.coverage.exclusions", "**/sample.xoo") + .build()) + .start(); + + InputFile file = result.inputFile("src/sample.xoo"); + assertThat(result.coverageFor(file, 2)).isNull(); + + Map<String, List<org.sonar.scanner.protocol.output.ScannerReport.Measure>> allMeasures = result.allMeasures(); + assertThat(allMeasures.get("com.foo.project:src/sample.xoo")).extracting("metricKey") + .doesNotContain(CoreMetrics.LINES_TO_COVER_KEY, CoreMetrics.UNCOVERED_LINES_KEY, CoreMetrics.CONDITIONS_TO_COVER_KEY, + CoreMetrics.COVERED_CONDITIONS_BY_LINE_KEY); + } + + @Test + public void fallbackOnExecutableLines() throws IOException { + + File baseDir = temp.getRoot(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + File measuresFile = new File(srcDir, "sample.xoo.measures"); + FileUtils.write(xooFile, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}"); + FileUtils.write(measuresFile, "executable_lines_data:2=1;3=1;4=0"); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .build()) + .start(); + + InputFile file = result.inputFile("src/sample.xoo"); + assertThat(result.coverageFor(file, 1)).isNull(); + + assertThat(result.coverageFor(file, 2).getUtHits()).isFalse(); + assertThat(result.coverageFor(file, 2).getItHits()).isFalse(); + assertThat(result.coverageFor(file, 2).getConditions()).isEqualTo(0); + assertThat(result.coverageFor(file, 2).getUtCoveredConditions()).isEqualTo(0); + assertThat(result.coverageFor(file, 2).getItCoveredConditions()).isEqualTo(0); + assertThat(result.coverageFor(file, 2).getOverallCoveredConditions()).isEqualTo(0); + + assertThat(result.coverageFor(file, 3).getUtHits()).isFalse(); + assertThat(result.coverageFor(file, 4)).isNull(); + + Map<String, List<org.sonar.scanner.protocol.output.ScannerReport.Measure>> allMeasures = result.allMeasures(); + assertThat(allMeasures.get("com.foo.project:src/sample.xoo")).extracting("metricKey", "intValue") + .contains(tuple(CoreMetrics.LINES_TO_COVER_KEY, 2), + tuple(CoreMetrics.UNCOVERED_LINES_KEY, 2)); + + assertThat(allMeasures.get("com.foo.project:src/sample.xoo")).extracting("metricKey").doesNotContain(CoreMetrics.CONDITIONS_TO_COVER_KEY, CoreMetrics.UNCOVERED_CONDITIONS_KEY); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/cpd/CpdMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/cpd/CpdMediumTest.java new file mode 100644 index 00000000000..662ed7dbcfd --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/cpd/CpdMediumTest.java @@ -0,0 +1,336 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.cpd; + +import com.google.common.collect.ImmutableMap; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import org.apache.commons.io.FileUtils; +import org.assertj.core.groups.Tuple; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.TaskResult; +import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.scanner.protocol.output.ScannerReport.Measure; +import org.sonar.xoo.XooPlugin; +import org.sonar.xoo.lang.CpdTokenizerSensor; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(Parameterized.class) +public class CpdMediumTest { + + @Parameters(name = "new api: {0}") + public static Collection<Object[]> data() { + return Arrays.asList(new Object[][] { + {true}, {false} + }); + } + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .registerPlugin("xoo", new XooPlugin()) + .addDefaultQProfile("xoo", "Sonar Way") + .build(); + + private File baseDir; + + private ImmutableMap.Builder<String, String> builder; + + private boolean useNewSensorApi; + + public CpdMediumTest(boolean useNewSensorApi) { + this.useNewSensorApi = useNewSensorApi; + } + + @Before + public void prepare() throws IOException { + tester.start(); + + baseDir = temp.getRoot(); + + builder = ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project"); + if (useNewSensorApi) { + builder.put(CpdTokenizerSensor.ENABLE_PROP, "true"); + } + } + + @After + public void stop() { + tester.stop(); + } + + @Test + public void testCrossModuleDuplications() throws IOException { + builder.put("sonar.modules", "module1,module2") + .put("sonar.cpd.xoo.minimumTokens", "10") + .put("sonar.verbose", "true"); + + // module 1 + builder.put("module1.sonar.projectKey", "module1"); + builder.put("module1.sonar.projectName", "Module 1"); + builder.put("module1.sonar.sources", "."); + + // module2 + builder.put("module2.sonar.projectKey", "module2"); + builder.put("module2.sonar.projectName", "Module 2"); + builder.put("module2.sonar.sources", "."); + + File module1Dir = new File(baseDir, "module1"); + File module2Dir = new File(baseDir, "module2"); + + module1Dir.mkdir(); + module2Dir.mkdir(); + + String duplicatedStuff = "Sample xoo\ncontent\n" + + "foo\nbar\ntoto\ntiti\n" + + "foo\nbar\ntoto\ntiti\n" + + "bar\ntoto\ntiti\n" + + "foo\nbar\ntoto\ntiti"; + + // create duplicated file in both modules + File xooFile1 = new File(module1Dir, "sample1.xoo"); + FileUtils.write(xooFile1, duplicatedStuff); + + File xooFile2 = new File(module2Dir, "sample2.xoo"); + FileUtils.write(xooFile2, duplicatedStuff); + + TaskResult result = tester.newTask().properties(builder.build()).start(); + + assertThat(result.inputFiles()).hasSize(2); + + InputFile inputFile1 = result.inputFile("sample1.xoo"); + InputFile inputFile2 = result.inputFile("sample2.xoo"); + + // One clone group on each file + List<org.sonar.scanner.protocol.output.ScannerReport.Duplication> duplicationGroupsFile1 = result.duplicationsFor(inputFile1); + assertThat(duplicationGroupsFile1).hasSize(1); + + org.sonar.scanner.protocol.output.ScannerReport.Duplication cloneGroupFile1 = duplicationGroupsFile1.get(0); + assertThat(cloneGroupFile1.getOriginPosition().getStartLine()).isEqualTo(1); + assertThat(cloneGroupFile1.getOriginPosition().getEndLine()).isEqualTo(17); + assertThat(cloneGroupFile1.getDuplicateList()).hasSize(1); + assertThat(cloneGroupFile1.getDuplicate(0).getOtherFileRef()).isEqualTo(result.getReportComponent(((DefaultInputFile) inputFile2).key()).getRef()); + + List<org.sonar.scanner.protocol.output.ScannerReport.Duplication> duplicationGroupsFile2 = result.duplicationsFor(inputFile2); + assertThat(duplicationGroupsFile2).hasSize(1); + + org.sonar.scanner.protocol.output.ScannerReport.Duplication cloneGroupFile2 = duplicationGroupsFile2.get(0); + assertThat(cloneGroupFile2.getOriginPosition().getStartLine()).isEqualTo(1); + assertThat(cloneGroupFile2.getOriginPosition().getEndLine()).isEqualTo(17); + assertThat(cloneGroupFile2.getDuplicateList()).hasSize(1); + assertThat(cloneGroupFile2.getDuplicate(0).getOtherFileRef()).isEqualTo(result.getReportComponent(((DefaultInputFile) inputFile1).key()).getRef()); + + assertThat(result.duplicationBlocksFor(inputFile1)).isEmpty(); + } + + @Test + public void testCrossFileDuplications() throws IOException { + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + String duplicatedStuff = "Sample xoo\ncontent\n" + + "foo\nbar\ntoto\ntiti\n" + + "foo\nbar\ntoto\ntiti\n" + + "bar\ntoto\ntiti\n" + + "foo\nbar\ntoto\ntiti"; + + File xooFile1 = new File(srcDir, "sample1.xoo"); + FileUtils.write(xooFile1, duplicatedStuff); + + File xooFile2 = new File(srcDir, "sample2.xoo"); + FileUtils.write(xooFile2, duplicatedStuff); + + TaskResult result = tester.newTask() + .properties(builder + .put("sonar.sources", "src") + .put("sonar.cpd.xoo.minimumTokens", "10") + .put("sonar.verbose", "true") + .build()) + .start(); + + assertThat(result.inputFiles()).hasSize(2); + + InputFile inputFile1 = result.inputFile("src/sample1.xoo"); + InputFile inputFile2 = result.inputFile("src/sample2.xoo"); + + // One clone group on each file + List<org.sonar.scanner.protocol.output.ScannerReport.Duplication> duplicationGroupsFile1 = result.duplicationsFor(inputFile1); + assertThat(duplicationGroupsFile1).hasSize(1); + + org.sonar.scanner.protocol.output.ScannerReport.Duplication cloneGroupFile1 = duplicationGroupsFile1.get(0); + assertThat(cloneGroupFile1.getOriginPosition().getStartLine()).isEqualTo(1); + assertThat(cloneGroupFile1.getOriginPosition().getEndLine()).isEqualTo(17); + assertThat(cloneGroupFile1.getDuplicateList()).hasSize(1); + assertThat(cloneGroupFile1.getDuplicate(0).getOtherFileRef()).isEqualTo(result.getReportComponent(((DefaultInputFile) inputFile2).key()).getRef()); + + List<org.sonar.scanner.protocol.output.ScannerReport.Duplication> duplicationGroupsFile2 = result.duplicationsFor(inputFile2); + assertThat(duplicationGroupsFile2).hasSize(1); + + org.sonar.scanner.protocol.output.ScannerReport.Duplication cloneGroupFile2 = duplicationGroupsFile2.get(0); + assertThat(cloneGroupFile2.getOriginPosition().getStartLine()).isEqualTo(1); + assertThat(cloneGroupFile2.getOriginPosition().getEndLine()).isEqualTo(17); + assertThat(cloneGroupFile2.getDuplicateList()).hasSize(1); + assertThat(cloneGroupFile2.getDuplicate(0).getOtherFileRef()).isEqualTo(result.getReportComponent(((DefaultInputFile) inputFile1).key()).getRef()); + + assertThat(result.duplicationBlocksFor(inputFile1)).isEmpty(); + } + + @Test + public void enableCrossProjectDuplication() throws IOException { + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + String duplicatedStuff = "Sample xoo\ncontent\nfoo\nbar\ntoto\ntiti\nfoo"; + + File xooFile1 = new File(srcDir, "sample1.xoo"); + FileUtils.write(xooFile1, duplicatedStuff); + + TaskResult result = tester.newTask() + .properties(builder + .put("sonar.sources", "src") + .put("sonar.cpd.xoo.minimumTokens", "1") + .put("sonar.cpd.xoo.minimumLines", "5") + .put("sonar.verbose", "true") + .put("sonar.cpd.cross_project", "true") + .build()) + .start(); + + InputFile inputFile1 = result.inputFile("src/sample1.xoo"); + + List<ScannerReport.CpdTextBlock> duplicationBlocks = result.duplicationBlocksFor(inputFile1); + assertThat(duplicationBlocks).hasSize(3); + assertThat(duplicationBlocks.get(0).getStartLine()).isEqualTo(1); + assertThat(duplicationBlocks.get(0).getEndLine()).isEqualTo(5); + assertThat(duplicationBlocks.get(0).getStartTokenIndex()).isEqualTo(1); + assertThat(duplicationBlocks.get(0).getEndTokenIndex()).isEqualTo(6); + assertThat(duplicationBlocks.get(0).getHash()).isNotEmpty(); + + assertThat(duplicationBlocks.get(1).getStartLine()).isEqualTo(2); + assertThat(duplicationBlocks.get(1).getEndLine()).isEqualTo(6); + assertThat(duplicationBlocks.get(1).getStartTokenIndex()).isEqualTo(3); + assertThat(duplicationBlocks.get(1).getEndTokenIndex()).isEqualTo(7); + assertThat(duplicationBlocks.get(0).getHash()).isNotEmpty(); + + assertThat(duplicationBlocks.get(2).getStartLine()).isEqualTo(3); + assertThat(duplicationBlocks.get(2).getEndLine()).isEqualTo(7); + assertThat(duplicationBlocks.get(2).getStartTokenIndex()).isEqualTo(4); + assertThat(duplicationBlocks.get(2).getEndTokenIndex()).isEqualTo(8); + assertThat(duplicationBlocks.get(0).getHash()).isNotEmpty(); + } + + // SONAR-6000 + @Test + public void truncateDuplication() throws IOException { + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + String duplicatedStuff = "Sample xoo\n"; + + int blockCount = 10000; + File xooFile1 = new File(srcDir, "sample.xoo"); + for (int i = 0; i < blockCount; i++) { + FileUtils.write(xooFile1, duplicatedStuff, true); + FileUtils.write(xooFile1, "" + i + "\n", true); + } + + TaskResult result = tester.newTask() + .properties(builder + .put("sonar.sources", "src") + .put("sonar.cpd.xoo.minimumTokens", "1") + .put("sonar.cpd.xoo.minimumLines", "1") + .build()) + .start(); + + Map<String, List<Measure>> allMeasures = result.allMeasures(); + + assertThat(allMeasures.get("com.foo.project")).extracting("metricKey", "intValue", "doubleValue", "stringValue").containsOnly( + Tuple.tuple(CoreMetrics.QUALITY_PROFILES_KEY, 0, 0.0, + "[{\"key\":\"Sonar Way\",\"language\":\"xoo\",\"name\":\"Sonar Way\",\"rulesUpdatedAt\":\"2009-02-13T23:31:31+0000\"}]")); + + assertThat(allMeasures.get("com.foo.project:src/sample.xoo")).extracting("metricKey", "intValue").containsOnly( + Tuple.tuple(CoreMetrics.LINES_KEY, blockCount * 2 + 1)); + + List<org.sonar.scanner.protocol.output.ScannerReport.Duplication> duplicationGroups = result.duplicationsFor(result.inputFile("src/sample.xoo")); + assertThat(duplicationGroups).hasSize(1); + + org.sonar.scanner.protocol.output.ScannerReport.Duplication cloneGroup = duplicationGroups.get(0); + assertThat(cloneGroup.getDuplicateList()).hasSize(100); + } + + @Test + public void testIntraFileDuplications() throws IOException { + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + String content = "Sample xoo\ncontent\nfoo\nbar\nSample xoo\ncontent\n"; + + File xooFile = new File(srcDir, "sample.xoo"); + FileUtils.write(xooFile, content); + + TaskResult result = tester.newTask() + .properties(builder + .put("sonar.sources", "src") + .put("sonar.cpd.xoo.minimumTokens", "2") + .put("sonar.cpd.xoo.minimumLines", "2") + .put("sonar.verbose", "true") + .build()) + .start(); + + InputFile inputFile = result.inputFile("src/sample.xoo"); + // One clone group + List<org.sonar.scanner.protocol.output.ScannerReport.Duplication> duplicationGroups = result.duplicationsFor(inputFile); + assertThat(duplicationGroups).hasSize(1); + + org.sonar.scanner.protocol.output.ScannerReport.Duplication cloneGroup = duplicationGroups.get(0); + assertThat(cloneGroup.getOriginPosition().getStartLine()).isEqualTo(1); + assertThat(cloneGroup.getOriginPosition().getEndLine()).isEqualTo(2); + assertThat(cloneGroup.getDuplicateList()).hasSize(1); + assertThat(cloneGroup.getDuplicate(0).getRange().getStartLine()).isEqualTo(5); + assertThat(cloneGroup.getDuplicate(0).getRange().getEndLine()).isEqualTo(6); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/deprecated/DeprecatedApiMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/deprecated/DeprecatedApiMediumTest.java new file mode 100644 index 00000000000..fa1ff38833d --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/deprecated/DeprecatedApiMediumTest.java @@ -0,0 +1,134 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.deprecated; + +import com.google.common.collect.ImmutableMap; +import java.io.File; +import java.io.IOException; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.TaskResult; +import org.sonar.xoo.XooPlugin; +import org.sonar.xoo.rule.XooRulesDefinition; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; + +public class DeprecatedApiMediumTest { + + @org.junit.Rule + public TemporaryFolder temp = new TemporaryFolder(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .registerPlugin("xoo", new XooPlugin()) + .addRules(new XooRulesDefinition()) + .addDefaultQProfile("xoo", "Sonar Way") + .addActiveRule("xoo", "DeprecatedResourceApi", null, "One issue per line", "MAJOR", null, "xoo") + .build(); + + @Before + public void prepare() { + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + + @Test + public void testIssueDetails() throws IOException { + + File baseDir = temp.getRoot(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFileInRootDir = new File(srcDir, "sample.xoo"); + FileUtils.write(xooFileInRootDir, "1\n2\n3\n4\n5\n6\n7\n8\n9\n10"); + + File xooFileInAnotherDir = new File(srcDir, "package/sample.xoo"); + FileUtils.write(xooFileInAnotherDir, "1\n2\n3\n4\n5\n6\n7\n8\n9\n10"); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .build()) + .start(); + + assertThat(result.issuesFor(result.inputFile("src/sample.xoo"))).extracting("msg", "line").containsOnly( + tuple("Issue created using deprecated API", 0), + tuple("Issue created using deprecated API", 1)); + assertThat(result.issuesFor(result.inputFile("src/package/sample.xoo"))).extracting("msg", "line").containsOnly( + tuple("Issue created using deprecated API", 0), + tuple("Issue created using deprecated API", 1)); + assertThat(result.issuesFor(result.inputDir("src"))).extracting("msg", "line").containsOnly( + tuple("Issue created using deprecated API", 0)); + assertThat(result.issuesFor(result.inputDir("src/package"))).extracting("msg", "line").containsOnly( + tuple("Issue created using deprecated API", 0)); + + } + + @Test + public void createIssueOnRootDir() throws IOException { + + File baseDir = temp.getRoot(); + + File xooFileInRootDir = new File(baseDir, "sample.xoo"); + FileUtils.write(xooFileInRootDir, "1\n2\n3\n4\n5\n6\n7\n8\n9\n10"); + + File xooFileInAnotherDir = new File(baseDir, "package/sample.xoo"); + FileUtils.write(xooFileInAnotherDir, "1\n2\n3\n4\n5\n6\n7\n8\n9\n10"); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", ".") + .build()) + .start(); + + assertThat(result.issuesFor(result.inputFile("sample.xoo"))).extracting("msg", "line").containsOnly( + tuple("Issue created using deprecated API", 0), + tuple("Issue created using deprecated API", 1)); + assertThat(result.issuesFor(result.inputFile("package/sample.xoo"))).extracting("msg", "line").containsOnly( + tuple("Issue created using deprecated API", 0), + tuple("Issue created using deprecated API", 1)); + assertThat(result.issuesFor(result.inputDir(""))).extracting("msg", "line").containsOnly( + tuple("Issue created using deprecated API", 0)); + assertThat(result.issuesFor(result.inputDir("package"))).extracting("msg", "line").containsOnly( + tuple("Issue created using deprecated API", 0)); + + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/fs/FileSystemMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/fs/FileSystemMediumTest.java new file mode 100644 index 00000000000..dcb32c1030e --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/fs/FileSystemMediumTest.java @@ -0,0 +1,296 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.fs; + +import com.google.common.collect.ImmutableMap; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.utils.MessageException; +import org.sonar.api.utils.System2; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.TaskResult; +import org.sonar.xoo.XooPlugin; + +import java.io.File; +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class FileSystemMediumTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .registerPlugin("xoo", new XooPlugin()) + .addDefaultQProfile("xoo", "Sonar Way") + .build(); + + private File baseDir; + + private ImmutableMap.Builder<String, String> builder; + + @Before + public void prepare() throws IOException { + tester.start(); + + baseDir = temp.getRoot(); + + builder = ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project"); + } + + @After + public void stop() { + tester.stop(); + } + + @Test + public void scanProjectWithSourceDir() throws IOException { + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + FileUtils.write(xooFile, "Sample xoo\ncontent"); + + TaskResult result = tester.newTask() + .properties(builder + .put("sonar.sources", "src") + .build()) + .start(); + + assertThat(result.inputFiles()).hasSize(1); + assertThat(result.inputDirs()).hasSize(1); + assertThat(result.inputFile("src/sample.xoo").type()).isEqualTo(InputFile.Type.MAIN); + assertThat(result.inputFile("src/sample.xoo").relativePath()).isEqualTo("src/sample.xoo"); + assertThat(result.inputDir("src").relativePath()).isEqualTo("src"); + } + + @Test + public void scanBigProject() throws IOException { + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + int nbFiles = 100; + int ruleCount = 100000; + for (int nb = 1; nb <= nbFiles; nb++) { + File xooFile = new File(srcDir, "sample" + nb + ".xoo"); + FileUtils.write(xooFile, StringUtils.repeat(StringUtils.repeat("a", 100) + "\n", ruleCount / 1000)); + } + + TaskResult result = tester.newTask() + .properties(builder + .put("sonar.sources", "src") + .build()) + .start(); + + assertThat(result.inputFiles()).hasSize(100); + assertThat(result.inputDirs()).hasSize(1); + } + + @Test + public void scanProjectWithTestDir() throws IOException { + File test = new File(baseDir, "test"); + test.mkdir(); + + File xooFile = new File(test, "sampleTest.xoo"); + FileUtils.write(xooFile, "Sample test xoo\ncontent"); + + TaskResult result = tester.newTask() + .properties(builder + .put("sonar.sources", "") + .put("sonar.tests", "test") + .build()) + .start(); + + assertThat(result.inputFiles()).hasSize(1); + assertThat(result.inputFile("test/sampleTest.xoo").type()).isEqualTo(InputFile.Type.TEST); + } + + /** + * SONAR-5419 + */ + @Test + public void scanProjectWithMixedSourcesAndTests() throws IOException { + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + FileUtils.write(xooFile, "Sample xoo\ncontent"); + + File xooFile2 = new File(baseDir, "another.xoo"); + FileUtils.write(xooFile2, "Sample xoo 2\ncontent"); + + File testDir = new File(baseDir, "test"); + testDir.mkdir(); + + File xooTestFile = new File(baseDir, "sampleTest2.xoo"); + FileUtils.write(xooTestFile, "Sample test xoo\ncontent"); + + File xooTestFile2 = new File(testDir, "sampleTest.xoo"); + FileUtils.write(xooTestFile2, "Sample test xoo 2\ncontent"); + + TaskResult result = tester.newTask() + .properties(builder + .put("sonar.sources", "src,another.xoo") + .put("sonar.tests", "test,sampleTest2.xoo") + .build()) + .start(); + + assertThat(result.inputFiles()).hasSize(4); + } + + @Test + public void fileInclusionsExclusions() throws IOException { + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + FileUtils.write(xooFile, "Sample xoo\ncontent"); + + File xooFile2 = new File(baseDir, "another.xoo"); + FileUtils.write(xooFile2, "Sample xoo 2\ncontent"); + + File testDir = new File(baseDir, "test"); + testDir.mkdir(); + + File xooTestFile = new File(baseDir, "sampleTest2.xoo"); + FileUtils.write(xooTestFile, "Sample test xoo\ncontent"); + + File xooTestFile2 = new File(testDir, "sampleTest.xoo"); + FileUtils.write(xooTestFile2, "Sample test xoo 2\ncontent"); + + TaskResult result = tester.newTask() + .properties(builder + .put("sonar.sources", "src,another.xoo") + .put("sonar.tests", "test,sampleTest2.xoo") + .put("sonar.inclusions", "src/**") + .put("sonar.exclusions", "**/another.*") + .put("sonar.test.inclusions", "**/sampleTest*.*") + .put("sonar.test.exclusions", "**/sampleTest2.xoo") + .build()) + .start(); + + assertThat(result.inputFiles()).hasSize(2); + } + + @Test + public void failForDuplicateInputFile() throws IOException { + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + FileUtils.write(xooFile, "Sample xoo\ncontent"); + + thrown.expect(MessageException.class); + thrown.expectMessage("can't be indexed twice. Please check that inclusion/exclusion patterns produce disjoint sets for main and test files"); + tester.newTask() + .properties(builder + .put("sonar.sources", "src,src/sample.xoo") + .build()) + .start(); + + } + + // SONAR-5330 + @Test + public void scanProjectWithSourceSymlink() { + if (!System2.INSTANCE.isOsWindows()) { + File projectDir = new File("src/test/resources/mediumtest/xoo/sample-with-symlink"); + TaskResult result = tester + .newScanTask(new File(projectDir, "sonar-project.properties")) + .start(); + + assertThat(result.inputFiles()).hasSize(3); + // check that symlink was not resolved to target + assertThat(result.inputFiles()).extractingResultOf("path").toString().startsWith(projectDir.toString()); + } + } + + // SONAR-6719 + @Test + public void scanProjectWithWrongCase() { + if (System2.INSTANCE.isOsWindows()) { + File projectDir = new File("src/test/resources/mediumtest/xoo/sample"); + TaskResult result = tester + .newScanTask(new File(projectDir, "sonar-project.properties")) + .property("sonar.sources", "XOURCES") + .property("sonar.tests", "TESTX") + .start(); + + assertThat(result.inputFiles()).hasSize(3); + assertThat(result.inputFiles()).extractingResultOf("relativePath").containsOnly( + "xources/hello/HelloJava.xoo", + "xources/hello/helloscala.xoo", + "testx/ClassOneTest.xoo"); + } + } + + @Test + public void indexAnyFile() throws IOException { + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + FileUtils.write(xooFile, "Sample xoo\ncontent"); + + File otherFile = new File(srcDir, "sample.other"); + FileUtils.write(otherFile, "Sample other\ncontent"); + + TaskResult result = tester.newTask() + .properties(builder + .put("sonar.sources", "src") + .put("sonar.import_unknown_files", "true") + .build()) + .start(); + + assertThat(result.inputFiles()).hasSize(2); + assertThat(result.inputFile("src/sample.other").type()).isEqualTo(InputFile.Type.MAIN); + assertThat(result.inputFile("src/sample.other").relativePath()).isEqualTo("src/sample.other"); + assertThat(result.inputFile("src/sample.other").language()).isNull(); + } + + @Test + public void scanMultiModuleProject() { + File projectDir = new File("src/test/resources/mediumtest/xoo/multi-modules-sample"); + TaskResult result = tester + .newScanTask(new File(projectDir, "sonar-project.properties")) + .start(); + + assertThat(result.inputFiles()).hasSize(4); + assertThat(result.inputDirs()).hasSize(4); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/fs/NoLanguagesPluginsMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/fs/NoLanguagesPluginsMediumTest.java new file mode 100644 index 00000000000..90502c54ae8 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/fs/NoLanguagesPluginsMediumTest.java @@ -0,0 +1,76 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.fs; + +import org.junit.rules.ExpectedException; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.filefilter.FileFilterUtils; +import org.sonar.batch.mediumtest.issuesmode.IssueModeAndReportsMediumTest; + +import java.io.File; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; +import org.sonar.batch.mediumtest.BatchMediumTester; + +public class NoLanguagesPluginsMediumTest { + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public ExpectedException exception = ExpectedException.none(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .setPreviousAnalysisDate(null) + .build(); + + @Before + public void prepare() { + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + + @Test + public void testNoLanguagePluginsInstalled() throws Exception { + File projectDir = copyProject("/mediumtest/xoo/sample"); + + exception.expect(IllegalStateException.class); + exception.expectMessage("No language plugins are installed"); + + tester + .newScanTask(new File(projectDir, "sonar-project.properties")) + .start(); + } + + private File copyProject(String path) throws Exception { + File projectDir = temp.newFolder(); + File originalProjectDir = new File(IssueModeAndReportsMediumTest.class.getResource(path).toURI()); + FileUtils.copyDirectory(originalProjectDir, projectDir, FileFilterUtils.notFileFilter(FileFilterUtils.nameFileFilter(".sonar"))); + return projectDir; + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/fs/ProjectBuilderMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/fs/ProjectBuilderMediumTest.java new file mode 100644 index 00000000000..21526ddcfd5 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/fs/ProjectBuilderMediumTest.java @@ -0,0 +1,168 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.fs; + +import com.google.common.collect.ImmutableMap; +import java.io.File; +import java.io.IOException; +import java.util.Date; +import java.util.List; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.utils.MessageException; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.TaskResult; +import org.sonar.scanner.protocol.output.ScannerReport.Issue; +import org.sonar.xoo.XooPlugin; +import org.sonar.xoo.rule.XooRulesDefinition; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ProjectBuilderMediumTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public ExpectedException exception = ExpectedException.none(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .registerPlugin("xoo", new XooPlugin()) + .addRules(new XooRulesDefinition()) + .addDefaultQProfile("xoo", "Sonar Way") + .setPreviousAnalysisDate(new Date()) + .addActiveRule("xoo", "OneIssuePerLine", null, "One issue per line", "MAJOR", "OneIssuePerLine.internal", "xoo") + .build(); + + @Before + public void prepare() { + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + + @Test + public void testProjectBuilder() throws IOException { + File baseDir = prepareProject(); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", ".") + .put("sonar.xoo.enableProjectBuilder", "true") + .build()) + .start(); + List<Issue> issues = result.issuesFor(result.inputFile("src/sample.xoo")); + assertThat(issues).hasSize(10); + + boolean foundIssueAtLine1 = false; + for (Issue issue : issues) { + if (issue.getLine() == 1) { + foundIssueAtLine1 = true; + assertThat(issue.getMsg()).isEqualTo("This issue is generated on each line"); + assertThat(issue.hasGap()).isFalse(); + } + } + assertThat(foundIssueAtLine1).isTrue(); + + } + + @Test + // SONAR-6976 + public void testProjectBuilderWithNewLine() throws IOException { + File baseDir = prepareProject(); + + exception.expect(MessageException.class); + exception.expectMessage("is not a valid branch name"); + tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.branch", "branch\n") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", ".") + .put("sonar.xoo.enableProjectBuilder", "true") + .build()) + .start(); + } + + @Test + public void testProjectBuilderWithBranch() throws IOException { + File baseDir = prepareProject(); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.branch", "my-branch") + .put("sonar.sources", ".") + .put("sonar.xoo.enableProjectBuilder", "true") + .build()) + .start(); + + List<Issue> issues = result.issuesFor(result.inputFile("src/sample.xoo")); + assertThat(issues).hasSize(10); + + boolean foundIssueAtLine1 = false; + for (Issue issue : issues) { + if (issue.getLine() == 1) { + foundIssueAtLine1 = true; + assertThat(issue.getMsg()).isEqualTo("This issue is generated on each line"); + assertThat(issue.hasGap()).isFalse(); + } + } + assertThat(foundIssueAtLine1).isTrue(); + } + + private File prepareProject() throws IOException { + File baseDir = temp.getRoot(); + File module1Dir = new File(baseDir, "module1"); + module1Dir.mkdir(); + + File srcDir = new File(module1Dir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + FileUtils.write(xooFile, "1\n2\n3\n4\n5\n6\n7\n8\n9\n10"); + + return baseDir; + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/highlighting/HighlightingMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/highlighting/HighlightingMediumTest.java new file mode 100644 index 00000000000..3c83ed61fcf --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/highlighting/HighlightingMediumTest.java @@ -0,0 +1,133 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.highlighting; + +import com.google.common.collect.ImmutableMap; +import java.io.File; +import java.io.IOException; +import org.apache.commons.io.FileUtils; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.sensor.highlighting.TypeOfText; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.TaskResult; +import org.sonar.xoo.XooPlugin; + +import static org.assertj.core.api.Assertions.assertThat; + +public class HighlightingMediumTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public ExpectedException exception = ExpectedException.none(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .registerPlugin("xoo", new XooPlugin()) + .addDefaultQProfile("xoo", "Sonar Way") + .build(); + + @Before + public void prepare() { + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + + @Test + public void computeSyntaxHighlightingOnTempProject() throws IOException { + + File baseDir = temp.newFolder(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + File xoohighlightingFile = new File(srcDir, "sample.xoo.highlighting"); + FileUtils.write(xooFile, "Sample xoo\ncontent plop"); + FileUtils.write(xoohighlightingFile, "0:10:s\n11:18:k"); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .build()) + .start(); + + InputFile file = result.inputFile("src/sample.xoo"); + assertThat(result.highlightingTypeFor(file, 1, 0)).containsExactly(TypeOfText.STRING); + assertThat(result.highlightingTypeFor(file, 1, 9)).containsExactly(TypeOfText.STRING); + assertThat(result.highlightingTypeFor(file, 2, 0)).containsExactly(TypeOfText.KEYWORD); + assertThat(result.highlightingTypeFor(file, 2, 8)).isEmpty(); + } + + @Test + public void computeInvalidOffsets() throws IOException { + + File baseDir = temp.newFolder(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + File xoohighlightingFile = new File(srcDir, "sample.xoo.highlighting"); + FileUtils.write(xooFile, "Sample xoo\ncontent plop"); + FileUtils.write(xoohighlightingFile, "0:10:s\n18:18:k"); + + exception.expect(IllegalStateException.class); + exception.expectMessage("Error processing line 2"); + exception.expectCause(new TypeSafeMatcher<IllegalArgumentException>() { + @Override + public void describeTo(Description description) { + description.appendText("Invalid cause"); + } + + @Override + protected boolean matchesSafely(IllegalArgumentException e) { + return e.getMessage().contains("Unable to highlight file"); + } + }); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .build()) + .start(); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issues/ChecksMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issues/ChecksMediumTest.java new file mode 100644 index 00000000000..804a66a7d4d --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issues/ChecksMediumTest.java @@ -0,0 +1,127 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.issues; + +import com.google.common.collect.ImmutableMap; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.rule.RuleKey; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.TaskResult; +import org.sonar.batch.rule.LoadedActiveRule; +import org.sonar.scanner.protocol.output.ScannerReport.Issue; +import org.sonar.xoo.XooPlugin; +import org.sonar.xoo.rule.XooRulesDefinition; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ChecksMediumTest { + + @org.junit.Rule + public TemporaryFolder temp = new TemporaryFolder(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .registerPlugin("xoo", new XooPlugin()) + .addRules(new XooRulesDefinition()) + .addDefaultQProfile("xoo", "Sonar Way") + .addRule("TemplateRule_1234", "xoo", "TemplateRule_1234", "A template rule") + .addRule("TemplateRule_1235", "xoo", "TemplateRule_1235", "Another template rule") + .activateRule(createActiveRuleWithParam("xoo", "TemplateRule_1234", "TemplateRule", "A template rule", "MAJOR", null, "xoo", "line", "1")) + .activateRule(createActiveRuleWithParam("xoo", "TemplateRule_1235", "TemplateRule", "Another template rule", "MAJOR", null, "xoo", "line", "2")) + .build(); + + @Before + public void prepare() { + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + + @Test + public void testCheckWithTemplate() throws IOException { + + File baseDir = temp.getRoot(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + FileUtils.write(xooFile, "foo\nbar"); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .build()) + .start(); + + List<Issue> issues = result.issuesFor(result.inputFile("src/sample.xoo")); + assertThat(issues).hasSize(2); + + boolean foundIssueAtLine1 = false; + boolean foundIssueAtLine2 = false; + for (Issue issue : issues) { + if (issue.getLine() == 1) { + foundIssueAtLine1 = true; + assertThat(issue.getMsg()).isEqualTo("A template rule"); + } + if (issue.getLine() == 2) { + foundIssueAtLine2 = true; + assertThat(issue.getMsg()).isEqualTo("Another template rule"); + } + } + assertThat(foundIssueAtLine1).isTrue(); + assertThat(foundIssueAtLine2).isTrue(); + } + + private LoadedActiveRule createActiveRuleWithParam(String repositoryKey, String ruleKey, @Nullable String templateRuleKey, String name, @Nullable String severity, + @Nullable String internalKey, @Nullable String languag, String paramKey, String paramValue) { + LoadedActiveRule r = new LoadedActiveRule(); + + r.setInternalKey(internalKey); + r.setRuleKey(RuleKey.of(repositoryKey, ruleKey)); + r.setName(name); + r.setTemplateRuleKey(templateRuleKey); + r.setLanguage(languag); + r.setSeverity(severity); + + Map<String, String> params = new HashMap<>(); + params.put(paramKey, paramValue); + r.setParams(params); + return r; + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issues/IssuesIssuesModeMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issues/IssuesIssuesModeMediumTest.java new file mode 100644 index 00000000000..cae361869cb --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issues/IssuesIssuesModeMediumTest.java @@ -0,0 +1,100 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.issues; + +import org.junit.rules.TemporaryFolder; +import org.sonar.batch.bootstrapper.IssueListener; +import org.junit.After; +import org.junit.Before; +import com.google.common.collect.ImmutableMap; +import org.sonar.api.CoreProperties; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.xoo.XooPlugin; +import org.sonar.xoo.rule.XooRulesDefinition; +import org.apache.commons.io.FileUtils; +import org.junit.Test; +import org.sonar.batch.mediumtest.TaskResult; + +import java.io.File; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class IssuesIssuesModeMediumTest { + @org.junit.Rule + public TemporaryFolder temp = new TemporaryFolder(); + + public BatchMediumTester testerPreview = BatchMediumTester.builder() + .registerPlugin("xoo", new XooPlugin()) + .addDefaultQProfile("xoo", "Sonar Way") + .addRules(new XooRulesDefinition()) + .bootstrapProperties(ImmutableMap.of(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_ISSUES)) + .addActiveRule("xoo", "OneIssuePerLine", null, "One issue per line", "MAJOR", "OneIssuePerLine.internal", "xoo") + .setLastBuildDate(new Date()) + .build(); + + @Before + public void prepare() { + testerPreview.start(); + } + + @After + public void stop() { + testerPreview.stop(); + } + + @Test + public void testIssueCallback() throws Exception { + File projectDir = new File(IssuesMediumTest.class.getResource("/mediumtest/xoo/sample").toURI()); + File tmpDir = temp.getRoot(); + FileUtils.copyDirectory(projectDir, tmpDir); + IssueRecorder issueListener = new IssueRecorder(); + + TaskResult result1 = testerPreview + .newScanTask(new File(tmpDir, "sonar-project.properties")) + .setIssueListener(issueListener) + .property(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_ISSUES) + .start(); + + assertThat(result1.trackedIssues()).hasSize(14); + assertThat(issueListener.issueList).hasSize(14); + issueListener = new IssueRecorder(); + + TaskResult result2 = testerPreview + .newScanTask(new File(tmpDir, "sonar-project.properties")) + .setIssueListener(issueListener) + .property(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_ISSUES) + .start(); + + assertThat(result2.trackedIssues()).hasSize(14); + assertThat(issueListener.issueList).hasSize(14); + } + + private class IssueRecorder implements IssueListener { + List<Issue> issueList = new LinkedList<>(); + + @Override + public void handle(Issue issue) { + issueList.add(issue); + } + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issues/IssuesMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issues/IssuesMediumTest.java new file mode 100644 index 00000000000..50823ff4f48 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issues/IssuesMediumTest.java @@ -0,0 +1,187 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.issues; + +import com.google.common.collect.ImmutableMap; +import java.io.File; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.batch.bootstrapper.IssueListener; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.TaskResult; +import org.sonar.scanner.protocol.output.ScannerReport.Issue; +import org.sonar.xoo.XooPlugin; +import org.sonar.xoo.rule.XooRulesDefinition; + +import static org.assertj.core.api.Assertions.assertThat; + +public class IssuesMediumTest { + + @org.junit.Rule + public TemporaryFolder temp = new TemporaryFolder(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .registerPlugin("xoo", new XooPlugin()) + .addDefaultQProfile("xoo", "Sonar Way") + .addRules(new XooRulesDefinition()) + .addActiveRule("xoo", "OneIssuePerLine", null, "One issue per line", "MAJOR", "OneIssuePerLine.internal", "xoo") + .build(); + + @Before + public void prepare() { + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + + @Test + public void testNoIssueCallbackInNonPreview() throws Exception { + File projectDir = new File(IssuesMediumTest.class.getResource("/mediumtest/xoo/sample").toURI()); + File tmpDir = temp.getRoot(); + FileUtils.copyDirectory(projectDir, tmpDir); + IssueRecorder issueListener = new IssueRecorder(); + + TaskResult result = tester + .newScanTask(new File(tmpDir, "sonar-project.properties")) + .setIssueListener(issueListener) + .start(); + + assertThat(result.issuesFor(result.inputFile("xources/hello/HelloJava.xoo"))).hasSize(8); + assertThat(issueListener.issueList).hasSize(0); + } + + @Test + public void testOneIssuePerLine() throws Exception { + File projectDir = new File(IssuesMediumTest.class.getResource("/mediumtest/xoo/sample").toURI()); + File tmpDir = temp.newFolder(); + FileUtils.copyDirectory(projectDir, tmpDir); + + TaskResult result = tester + .newScanTask(new File(tmpDir, "sonar-project.properties")) + .start(); + + List<Issue> issues = result.issuesFor(result.inputFile("xources/hello/HelloJava.xoo")); + assertThat(issues).hasSize(8 /* lines */); + + Issue issue = issues.get(0); + assertThat(issue.getTextRange().getStartLine()).isEqualTo(issue.getLine()); + assertThat(issue.getTextRange().getEndLine()).isEqualTo(issue.getLine()); + } + + @Test + public void findActiveRuleByInternalKey() throws Exception { + File projectDir = new File(IssuesMediumTest.class.getResource("/mediumtest/xoo/sample").toURI()); + File tmpDir = temp.newFolder(); + FileUtils.copyDirectory(projectDir, tmpDir); + + TaskResult result = tester + .newScanTask(new File(tmpDir, "sonar-project.properties")) + .property("sonar.xoo.internalKey", "OneIssuePerLine.internal") + .start(); + + List<Issue> issues = result.issuesFor(result.inputFile("xources/hello/HelloJava.xoo")); + assertThat(issues).hasSize(8 /* lines */ + 1 /* file */); + } + + @Test + public void testOverrideQProfileSeverity() throws Exception { + File projectDir = new File(IssuesMediumTest.class.getResource("/mediumtest/xoo/sample").toURI()); + File tmpDir = temp.newFolder(); + FileUtils.copyDirectory(projectDir, tmpDir); + + TaskResult result = tester + .newScanTask(new File(tmpDir, "sonar-project.properties")) + .property("sonar.oneIssuePerLine.forceSeverity", "CRITICAL") + .start(); + + List<Issue> issues = result.issuesFor(result.inputFile("xources/hello/HelloJava.xoo")); + assertThat(issues.get(0).getSeverity()).isEqualTo(org.sonar.scanner.protocol.Constants.Severity.CRITICAL); + } + + @Test + public void testIssueExclusion() throws Exception { + File projectDir = new File(IssuesMediumTest.class.getResource("/mediumtest/xoo/sample").toURI()); + File tmpDir = temp.newFolder(); + FileUtils.copyDirectory(projectDir, tmpDir); + + TaskResult result = tester + .newScanTask(new File(tmpDir, "sonar-project.properties")) + .property("sonar.issue.ignore.allfile", "1") + .property("sonar.issue.ignore.allfile.1.fileRegexp", "object") + .start(); + + assertThat(result.issuesFor(result.inputFile("xources/hello/HelloJava.xoo"))).hasSize(8 /* lines */); + assertThat(result.issuesFor(result.inputFile("xources/hello/helloscala.xoo"))).isEmpty(); + } + + @Test + public void testIssueDetails() throws IOException { + + File baseDir = temp.newFolder(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + FileUtils.write(xooFile, "1\n2\n3\n4\n5\n6\n7\n8\n9\n10"); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .build()) + .start(); + + List<Issue> issues = result.issuesFor(result.inputFile("src/sample.xoo")); + assertThat(issues).hasSize(10); + + boolean foundIssueAtLine1 = false; + for (Issue issue : issues) { + if (issue.getLine() == 1) { + foundIssueAtLine1 = true; + assertThat(issue.getMsg()).isEqualTo("This issue is generated on each line"); + assertThat(issue.hasGap()).isFalse(); + } + } + assertThat(foundIssueAtLine1).isTrue(); + } + + private class IssueRecorder implements IssueListener { + List<Issue> issueList = new LinkedList<>(); + + @Override + public void handle(Issue issue) { + issueList.add(issue); + } + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issues/IssuesOnDirMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issues/IssuesOnDirMediumTest.java new file mode 100644 index 00000000000..aae11ca0e39 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issues/IssuesOnDirMediumTest.java @@ -0,0 +1,113 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.issues; + +import com.google.common.collect.ImmutableMap; +import java.io.File; +import java.io.IOException; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.TaskResult; +import org.sonar.xoo.XooPlugin; +import org.sonar.xoo.rule.XooRulesDefinition; + +import static org.assertj.core.api.Assertions.assertThat; + +public class IssuesOnDirMediumTest { + + @org.junit.Rule + public TemporaryFolder temp = new TemporaryFolder(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .registerPlugin("xoo", new XooPlugin()) + .addDefaultQProfile("xoo", "Sonar Way") + .addRules(new XooRulesDefinition()) + .addActiveRule("xoo", "OneIssueOnDirPerFile", null, "One issue per line", "MINOR", "xoo", "xoo") + .build(); + + @Before + public void prepare() { + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + + @Test + public void scanTempProject() throws IOException { + + File baseDir = temp.getRoot(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile1 = new File(srcDir, "sample1.xoo"); + FileUtils.write(xooFile1, "Sample1 xoo\ncontent"); + + File xooFile2 = new File(srcDir, "sample2.xoo"); + FileUtils.write(xooFile2, "Sample2 xoo\ncontent"); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .build()) + .start(); + + assertThat(result.issuesFor(result.inputDir("src"))).hasSize(2); + } + + @Test + public void issueOnRootFolder() throws IOException { + + File baseDir = temp.getRoot(); + + File xooFile1 = new File(baseDir, "sample1.xoo"); + FileUtils.write(xooFile1, "Sample1 xoo\ncontent"); + + File xooFile2 = new File(baseDir, "sample2.xoo"); + FileUtils.write(xooFile2, "Sample2 xoo\ncontent"); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", ".") + .build()) + .start(); + + assertThat(result.issuesFor(result.inputDir(""))).hasSize(2); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issues/IssuesOnModuleMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issues/IssuesOnModuleMediumTest.java new file mode 100644 index 00000000000..73434ed872d --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issues/IssuesOnModuleMediumTest.java @@ -0,0 +1,84 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.issues; + +import com.google.common.collect.ImmutableMap; +import java.io.File; +import java.io.IOException; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.TaskResult; +import org.sonar.xoo.XooPlugin; +import org.sonar.xoo.rule.XooRulesDefinition; + +import static org.assertj.core.api.Assertions.assertThat; + +public class IssuesOnModuleMediumTest { + + @org.junit.Rule + public TemporaryFolder temp = new TemporaryFolder(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .registerPlugin("xoo", new XooPlugin()) + .addDefaultQProfile("xoo", "Sonar Way") + .addRules(new XooRulesDefinition()) + .addActiveRule("xoo", "OneIssuePerModule", null, "One issue per module", "MINOR", "xoo", "xoo") + .build(); + + @Before + public void prepare() { + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + + @Test + public void scanTempProject() throws IOException { + + File baseDir = temp.getRoot(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile1 = new File(srcDir, "sample1.xoo"); + FileUtils.write(xooFile1, "Sample1 xoo\ncontent"); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .build()) + .start(); + + assertThat(result.issuesFor(result.getReportComponent("com.foo.project"))).hasSize(1); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issues/MultilineIssuesMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issues/MultilineIssuesMediumTest.java new file mode 100644 index 00000000000..7ec0fe90535 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issues/MultilineIssuesMediumTest.java @@ -0,0 +1,131 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.issues; + +import java.io.File; +import java.util.List; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.TaskResult; +import org.sonar.scanner.protocol.output.ScannerReport.Flow; +import org.sonar.scanner.protocol.output.ScannerReport.Issue; +import org.sonar.scanner.protocol.output.ScannerReport.IssueLocation; +import org.sonar.xoo.XooPlugin; +import org.sonar.xoo.rule.XooRulesDefinition; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MultilineIssuesMediumTest { + + @org.junit.Rule + public TemporaryFolder temp = new TemporaryFolder(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .registerPlugin("xoo", new XooPlugin()) + .addRules(new XooRulesDefinition()) + .addDefaultQProfile("xoo", "Sonar Way") + .addActiveRule("xoo", "MultilineIssue", null, "Multinile Issue", "MAJOR", null, "xoo") + .build(); + + private TaskResult result; + + @Before + public void prepare() throws Exception { + tester.start(); + + File projectDir = new File(MultilineIssuesMediumTest.class.getResource("/mediumtest/xoo/sample-multiline").toURI()); + File tmpDir = temp.getRoot(); + FileUtils.copyDirectory(projectDir, tmpDir); + + result = tester + .newScanTask(new File(tmpDir, "sonar-project.properties")) + .start(); + } + + @After + public void stop() { + tester.stop(); + } + + @Test + public void testIssueRange() throws Exception { + List<Issue> issues = result.issuesFor(result.inputFile("xources/hello/Single.xoo")); + assertThat(issues).hasSize(1); + Issue issue = issues.get(0); + assertThat(issue.getLine()).isEqualTo(6); + assertThat(issue.getMsg()).isEqualTo("Primary location"); + assertThat(issue.getTextRange().getStartLine()).isEqualTo(6); + assertThat(issue.getTextRange().getStartOffset()).isEqualTo(23); + assertThat(issue.getTextRange().getEndLine()).isEqualTo(6); + assertThat(issue.getTextRange().getEndOffset()).isEqualTo(50); + } + + @Test + public void testMultilineIssueRange() throws Exception { + List<Issue> issues = result.issuesFor(result.inputFile("xources/hello/Multiline.xoo")); + assertThat(issues).hasSize(1); + Issue issue = issues.get(0); + assertThat(issue.getLine()).isEqualTo(6); + assertThat(issue.getMsg()).isEqualTo("Primary location"); + assertThat(issue.getTextRange().getStartLine()).isEqualTo(6); + assertThat(issue.getTextRange().getStartOffset()).isEqualTo(23); + assertThat(issue.getTextRange().getEndLine()).isEqualTo(7); + assertThat(issue.getTextRange().getEndOffset()).isEqualTo(23); + } + + @Test + public void testFlowWithSingleLocation() throws Exception { + List<Issue> issues = result.issuesFor(result.inputFile("xources/hello/Multiple.xoo")); + assertThat(issues).hasSize(1); + Issue issue = issues.get(0); + assertThat(issue.getLine()).isEqualTo(6); + assertThat(issue.getMsg()).isEqualTo("Primary location"); + assertThat(issue.getTextRange().getStartLine()).isEqualTo(6); + assertThat(issue.getTextRange().getStartOffset()).isEqualTo(23); + assertThat(issue.getTextRange().getEndLine()).isEqualTo(6); + assertThat(issue.getTextRange().getEndOffset()).isEqualTo(50); + + assertThat(issue.getFlowList()).hasSize(1); + Flow flow = issue.getFlow(0); + assertThat(flow.getLocationList()).hasSize(1); + IssueLocation additionalLocation = flow.getLocation(0); + assertThat(additionalLocation.getMsg()).isEqualTo("Flow step #1"); + assertThat(additionalLocation.getTextRange().getStartLine()).isEqualTo(7); + assertThat(additionalLocation.getTextRange().getStartOffset()).isEqualTo(26); + assertThat(additionalLocation.getTextRange().getEndLine()).isEqualTo(7); + assertThat(additionalLocation.getTextRange().getEndOffset()).isEqualTo(53); + } + + @Test + public void testFlowsWithMultipleElements() throws Exception { + List<Issue> issues = result.issuesFor(result.inputFile("xources/hello/WithFlow.xoo")); + assertThat(issues).hasSize(1); + Issue issue = issues.get(0); + assertThat(issue.getFlowList()).hasSize(1); + + Flow flow = issue.getFlow(0); + assertThat(flow.getLocationList()).hasSize(2); + // TODO more assertions + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issuesmode/EmptyFileTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issuesmode/EmptyFileTest.java new file mode 100644 index 00000000000..22eb34b4c22 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issuesmode/EmptyFileTest.java @@ -0,0 +1,94 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.issuesmode; + +import org.sonar.batch.issue.tracking.TrackedIssue; + +import org.apache.commons.io.filefilter.FileFilterUtils; +import org.apache.commons.io.FileUtils; +import org.sonar.xoo.rule.XooRulesDefinition; +import com.google.common.collect.ImmutableMap; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.CoreProperties; +import org.sonar.api.utils.log.LogTester; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.TaskResult; +import org.sonar.xoo.XooPlugin; + +import java.io.File; +import java.util.Date; + +import static org.assertj.core.api.Assertions.assertThat; + +public class EmptyFileTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public LogTester logTester = new LogTester(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .bootstrapProperties(ImmutableMap.of(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_ISSUES)) + .registerPlugin("xoo", new XooPlugin()) + .addRules(new XooRulesDefinition()) + .addDefaultQProfile("xoo", "Sonar Way") + .addActiveRule("xoo", "OneIssuePerLine", null, "One issue per line", "MAJOR", "my/internal/key", "xoo") + .setPreviousAnalysisDate(new Date()) + .build(); + + @Before + public void prepare() { + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + + @Test + public void testIssueTrackingWithIssueOnEmptyFile() throws Exception { + File projectDir = copyProject("/mediumtest/xoo/sample-with-empty-file"); + + TaskResult result = tester + .newScanTask(new File(projectDir, "sonar-project.properties")) + .property("sonar.xoo.internalKey", "my/internal/key") + .start(); + + for(TrackedIssue i : result.trackedIssues()) { + System.out.println(i.startLine() + " " + i.getMessage()); + } + + assertThat(result.trackedIssues()).hasSize(11); + } + + private File copyProject(String path) throws Exception { + File projectDir = temp.newFolder(); + File originalProjectDir = new File(EmptyFileTest.class.getResource(path).toURI()); + FileUtils.copyDirectory(originalProjectDir, projectDir, FileFilterUtils.notFileFilter(FileFilterUtils.nameFileFilter(".sonar"))); + return projectDir; + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issuesmode/IssueModeAndReportsMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issuesmode/IssueModeAndReportsMediumTest.java new file mode 100644 index 00000000000..e02c84ec71a --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issuesmode/IssueModeAndReportsMediumTest.java @@ -0,0 +1,316 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.issuesmode; + +import org.apache.commons.lang.StringUtils; + +import org.sonar.api.utils.log.LoggerLevel; +import org.assertj.core.api.Condition; +import org.sonar.batch.issue.tracking.TrackedIssue; +import com.google.common.collect.ImmutableMap; + +import java.io.File; +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.filefilter.FileFilterUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.CoreProperties; +import org.sonar.api.utils.log.LogTester; +import org.sonar.batch.bootstrapper.IssueListener; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.TaskResult; +import org.sonar.batch.scan.report.ConsoleReport; +import org.sonar.scanner.protocol.Constants.Severity; +import org.sonar.scanner.protocol.input.ScannerInput.ServerIssue; +import org.sonar.xoo.XooPlugin; +import org.sonar.xoo.rule.XooRulesDefinition; +import static org.assertj.core.api.Assertions.assertThat; + +public class IssueModeAndReportsMediumTest { + + @org.junit.Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @org.junit.Rule + public LogTester logTester = new LogTester(); + + private static SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); + + private static Long date(String date) { + try { + return sdf.parse(date).getTime(); + } catch (ParseException e) { + throw new IllegalStateException(e); + } + } + + public BatchMediumTester tester = BatchMediumTester.builder() + .bootstrapProperties(ImmutableMap.of(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_ISSUES)) + .registerPlugin("xoo", new XooPlugin()) + .addDefaultQProfile("xoo", "Sonar Way") + .addRules(new XooRulesDefinition()) + .addRule("manual:MyManualIssue", "manual", "MyManualIssue", "My manual issue") + .addRule("manual:MyManualIssueDup", "manual", "MyManualIssue", "My manual issue") + .addActiveRule("xoo", "OneIssuePerLine", null, "One issue per line", "MAJOR", null, "xoo") + .addActiveRule("xoo", "OneIssueOnDirPerFile", null, "OneIssueOnDirPerFile", "MAJOR", null, "xoo") + .addActiveRule("xoo", "OneIssuePerModule", null, "OneIssuePerModule", "MAJOR", null, "xoo") + .addActiveRule("manual", "MyManualIssue", null, "My manual issue", "MAJOR", null, null) + .setPreviousAnalysisDate(new Date()) + // Existing issue that is still detected + .mockServerIssue(ServerIssue.newBuilder().setKey("xyz") + .setModuleKey("sample") + .setPath("xources/hello/HelloJava.xoo") + .setRuleRepository("xoo") + .setRuleKey("OneIssuePerLine") + .setLine(1) + .setSeverity(Severity.MAJOR) + .setCreationDate(date("14/03/2004")) + .setChecksum(DigestUtils.md5Hex("packagehello;")) + .setStatus("OPEN") + .build()) + // Existing issue that is no more detected (will be closed) + .mockServerIssue(ServerIssue.newBuilder().setKey("resolved") + .setModuleKey("sample") + .setPath("xources/hello/HelloJava.xoo") + .setRuleRepository("xoo") + .setRuleKey("OneIssuePerLine") + .setLine(1) + .setSeverity(Severity.MAJOR) + .setCreationDate(date("14/03/2004")) + .setChecksum(DigestUtils.md5Hex("dontexist")) + .setStatus("OPEN") + .build()) + // Existing issue on project that is still detected + .mockServerIssue(ServerIssue.newBuilder().setKey("resolved-on-project") + .setModuleKey("sample") + .setRuleRepository("xoo") + .setRuleKey("OneIssuePerModule") + .setSeverity(Severity.CRITICAL) + .setCreationDate(date("14/03/2004")) + .setStatus("OPEN") + .build()) + // Manual issue + .mockServerIssue(ServerIssue.newBuilder().setKey("manual") + .setModuleKey("sample") + .setPath("xources/hello/HelloJava.xoo") + .setRuleRepository("manual") + .setRuleKey("MyManualIssue") + .setLine(1) + .setSeverity(Severity.MAJOR) + .setCreationDate(date("14/03/2004")) + .setChecksum(DigestUtils.md5Hex("packagehello;")) + .setStatus("OPEN") + .build()) + .build(); + + @Before + public void prepare() { + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + + private File copyProject(String path) throws Exception { + File projectDir = temp.newFolder(); + File originalProjectDir = new File(IssueModeAndReportsMediumTest.class.getResource(path).toURI()); + FileUtils.copyDirectory(originalProjectDir, projectDir, FileFilterUtils.notFileFilter(FileFilterUtils.nameFileFilter(".sonar"))); + return projectDir; + } + + @Test + public void testIssueTracking() throws Exception { + File projectDir = copyProject("/mediumtest/xoo/sample"); + + TaskResult result = tester + .newScanTask(new File(projectDir, "sonar-project.properties")) + .start(); + + int newIssues = 0; + int openIssues = 0; + int resolvedIssue = 0; + for (TrackedIssue issue : result.trackedIssues()) { + System.out + .println(issue.getMessage() + " " + issue.key() + " " + issue.getRuleKey() + " " + issue.isNew() + " " + issue.resolution() + " " + issue.componentKey() + " " + + issue.startLine()); + if (issue.isNew()) { + newIssues++; + } else if (issue.resolution() != null) { + resolvedIssue++; + } else { + openIssues++; + } + } + System.out.println("new: " + newIssues + " open: " + openIssues + " resolved " + resolvedIssue); + assertThat(newIssues).isEqualTo(16); + assertThat(openIssues).isEqualTo(3); + assertThat(resolvedIssue).isEqualTo(1); + + // progress report + String logs = StringUtils.join(logTester.logs(LoggerLevel.INFO), "\n"); + + assertThat(logs).contains("Performing issue tracking"); + assertThat(logs).contains("6/6 components tracked"); + + // assert that original fields of a matched issue are kept + assertThat(result.trackedIssues()).haveExactly(1, new Condition<TrackedIssue>() { + @Override + public boolean matches(TrackedIssue value) { + return value.isNew() == false + && "resolved-on-project".equals(value.key()) + && "OPEN".equals(value.status()) + && new Date(date("14/03/2004")).equals(value.creationDate()); + } + }); + } + + @Test + public void testConsoleReport() throws Exception { + File projectDir = copyProject("/mediumtest/xoo/sample"); + + tester + .newScanTask(new File(projectDir, "sonar-project.properties")) + .property("sonar.issuesReport.console.enable", "true") + .start(); + + assertThat(getReportLog()).contains("+16 issues", "+16 major"); + } + + @Test + public void testPostJob() throws Exception { + File projectDir = copyProject("/mediumtest/xoo/sample"); + + tester + .newScanTask(new File(projectDir, "sonar-project.properties")) + .property("sonar.xoo.enablePostJob", "true") + .start(); + + assertThat(logTester.logs()).contains("Resolved issues: 1", "Open issues: 19"); + } + + private String getReportLog() { + for (String log : logTester.logs()) { + if (log.contains(ConsoleReport.HEADER)) { + return log; + } + } + throw new IllegalStateException("No console report"); + } + + @Test + public void testHtmlReport() throws Exception { + File projectDir = copyProject("/mediumtest/xoo/sample"); + + tester + .newScanTask(new File(projectDir, "sonar-project.properties")) + .property("sonar.issuesReport.html.enable", "true") + .start(); + + assertThat(new File(projectDir, ".sonar/issues-report/issues-report.html")).exists(); + assertThat(new File(projectDir, ".sonar/issues-report/issues-report-light.html")).exists(); + } + + @Test + public void testHtmlReportNoFile() throws Exception { + File baseDir = temp.newFolder(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "sample") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .put("sonar.issuesReport.html.enable", "true") + .build()) + .start(); + + assertThat(FileUtils.readFileToString(new File(baseDir, ".sonar/issues-report/issues-report.html"))).contains("No file analyzed"); + assertThat(FileUtils.readFileToString(new File(baseDir, ".sonar/issues-report/issues-report-light.html"))).contains("No file analyzed"); + } + + @Test + public void testIssueCallback() throws Exception { + File projectDir = copyProject("/mediumtest/xoo/sample"); + IssueRecorder issueListener = new IssueRecorder(); + + TaskResult result = tester + .newScanTask(new File(projectDir, "sonar-project.properties")) + .setIssueListener(issueListener) + .start(); + + assertThat(result.trackedIssues()).hasSize(20); + assertThat(issueListener.issueList).hasSize(20); + } + + private class IssueRecorder implements IssueListener { + List<Issue> issueList = new LinkedList<>(); + + @Override + public void handle(Issue issue) { + issueList.add(issue); + } + } + + @Test + public void noSyntaxHighlightingInIssuesMode() throws IOException { + + File baseDir = temp.newFolder(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + File xoohighlightingFile = new File(srcDir, "sample.xoo.highlighting"); + FileUtils.write(xooFile, "Sample xoo\ncontent plop"); + FileUtils.write(xoohighlightingFile, "0:10:s\n11:18:k"); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .build()) + .start(); + + assertThat(result.getReportReader().hasSyntaxHighlighting(1)).isFalse(); + assertThat(result.getReportReader().hasSyntaxHighlighting(2)).isFalse(); + assertThat(result.getReportReader().hasSyntaxHighlighting(3)).isFalse(); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issuesmode/NoPreviousAnalysisTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issuesmode/NoPreviousAnalysisTest.java new file mode 100644 index 00000000000..fa133979562 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issuesmode/NoPreviousAnalysisTest.java @@ -0,0 +1,86 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.issuesmode; + +import org.sonar.batch.mediumtest.TaskResult; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.filefilter.FileFilterUtils; + +import java.io.File; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.common.collect.ImmutableMap; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.CoreProperties; +import org.sonar.api.utils.log.LogTester; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.xoo.XooPlugin; +import org.sonar.xoo.rule.XooRulesDefinition; + +public class NoPreviousAnalysisTest { + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public LogTester logTester = new LogTester(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .bootstrapProperties(ImmutableMap.of(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_ISSUES)) + .registerPlugin("xoo", new XooPlugin()) + .addRules(new XooRulesDefinition()) + .addDefaultQProfile("xoo", "Sonar Way") + .addActiveRule("xoo", "OneIssuePerLine", null, "One issue per line", "MAJOR", "my/internal/key", "xoo") + .setPreviousAnalysisDate(null) + .build(); + + @Before + public void prepare() { + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + + @Test + public void testIssueTrackingWithIssueOnEmptyFile() throws Exception { + File projectDir = copyProject("/mediumtest/xoo/sample"); + + TaskResult result = tester + .newScanTask(new File(projectDir, "sonar-project.properties")) + .start(); + + assertThat(result.trackedIssues()).hasSize(14); + + } + + private File copyProject(String path) throws Exception { + File projectDir = temp.newFolder(); + File originalProjectDir = new File(IssueModeAndReportsMediumTest.class.getResource(path).toURI()); + FileUtils.copyDirectory(originalProjectDir, projectDir, FileFilterUtils.notFileFilter(FileFilterUtils.nameFileFilter(".sonar"))); + return projectDir; + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issuesmode/NonAssociatedProject.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issuesmode/NonAssociatedProject.java new file mode 100644 index 00000000000..32c66f2689f --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issuesmode/NonAssociatedProject.java @@ -0,0 +1,89 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.issuesmode; + +import com.google.common.collect.ImmutableMap; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.filefilter.FileFilterUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.CoreProperties; +import org.sonar.api.utils.log.LogTester; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.TaskResult; +import org.sonar.xoo.XooPlugin; +import org.sonar.xoo.rule.XooRulesDefinition; + +import java.io.File; +import java.io.IOException; + +public class NonAssociatedProject { + @org.junit.Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @org.junit.Rule + public LogTester logTester = new LogTester(); + + public BatchMediumTester tester; + + @Before + public void prepare() throws IOException { + tester = BatchMediumTester.builder() + .bootstrapProperties(ImmutableMap.of( + CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_ISSUES, + CoreProperties.GLOBAL_WORKING_DIRECTORY, temp.newFolder().getAbsolutePath())) + .registerPlugin("xoo", new XooPlugin()) + .addQProfile("xoo", "Sonar Way") + .addRules(new XooRulesDefinition()) + .addRule("manual:MyManualIssue", "manual", "MyManualIssue", "My manual issue") + .addRule("manual:MyManualIssueDup", "manual", "MyManualIssue", "My manual issue") + .addActiveRule("xoo", "OneIssuePerLine", null, "One issue per line", "MAJOR", null, "xoo") + .addActiveRule("xoo", "OneIssueOnDirPerFile", null, "OneIssueOnDirPerFile", "MAJOR", null, "xoo") + .addActiveRule("xoo", "OneIssuePerModule", null, "OneIssuePerModule", "MAJOR", null, "xoo") + .addActiveRule("manual", "MyManualIssue", null, "My manual issue", "MAJOR", null, null) + .setAssociated(false) + .build(); + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + + private File copyProject(String path) throws Exception { + File projectDir = temp.newFolder(); + File originalProjectDir = new File(IssueModeAndReportsMediumTest.class.getResource(path).toURI()); + FileUtils.copyDirectory(originalProjectDir, projectDir, FileFilterUtils.notFileFilter(FileFilterUtils.nameFileFilter(".sonar"))); + return projectDir; + } + + @Test + public void testNonAssociated() throws Exception { + File projectDir = copyProject("/mediumtest/xoo/multi-modules-sample-not-associated"); + + TaskResult result = tester + .newScanTask(new File(projectDir, "sonar-project.properties")) + .start(); + + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issuesmode/ScanOnlyChangedTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issuesmode/ScanOnlyChangedTest.java new file mode 100644 index 00000000000..eb5850b0809 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/issuesmode/ScanOnlyChangedTest.java @@ -0,0 +1,212 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.issuesmode; + +import org.sonar.batch.issue.tracking.TrackedIssue; + +import org.assertj.core.api.Condition; +import com.google.common.io.Resources; +import org.sonar.batch.repository.FileData; +import org.sonar.scanner.protocol.Constants.Severity; +import org.sonar.scanner.protocol.input.ScannerInput.ServerIssue; +import com.google.common.collect.ImmutableMap; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.filefilter.FileFilterUtils; +import org.junit.After; +import org.junit.Before; +import org.sonar.api.CoreProperties; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.xoo.XooPlugin; +import org.sonar.xoo.rule.XooRulesDefinition; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.utils.log.LogTester; +import org.junit.Test; +import org.sonar.batch.mediumtest.TaskResult; + +import java.io.File; +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ScanOnlyChangedTest { + @org.junit.Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @org.junit.Rule + public LogTester logTester = new LogTester(); + + private static SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); + + private BatchMediumTester tester; + + private static Long date(String date) { + try { + return sdf.parse(date).getTime(); + } catch (ParseException e) { + throw new IllegalStateException(e); + } + } + + @Before + public void prepare() throws IOException { + String filePath = "xources/hello/HelloJava.xoo"; + String md5sum = DigestUtils.md5Hex(FileUtils.readFileToString(new File( + Resources.getResource("mediumtest/xoo/sample/" + filePath).getPath()))); + + tester = BatchMediumTester.builder() + .bootstrapProperties(ImmutableMap.of(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_ISSUES)) + .registerPlugin("xoo", new XooPlugin()) + .addDefaultQProfile("xoo", "Sonar Way") + .addRules(new XooRulesDefinition()) + .addRule("manual:MyManualIssue", "manual", "MyManualIssue", "My manual issue") + .addRule("manual:MyManualIssueDup", "manual", "MyManualIssue", "My manual issue") + .addActiveRule("xoo", "OneIssuePerLine", null, "One issue per line", "MAJOR", null, "xoo") + .addActiveRule("xoo", "OneIssueOnDirPerFile", null, "OneIssueOnDirPerFile", "MAJOR", null, "xoo") + .addActiveRule("xoo", "OneIssuePerModule", null, "OneIssuePerModule", "MAJOR", null, "xoo") + .addActiveRule("manual", "MyManualIssue", null, "My manual issue", "MAJOR", null, null) + // this will cause the file to have status==SAME + .addFileData("sample", filePath, new FileData(md5sum, null)) + .setPreviousAnalysisDate(new Date()) + // Existing issue that is copied + .mockServerIssue(ServerIssue.newBuilder().setKey("xyz") + .setModuleKey("sample") + .setMsg("One issue per Line copied") + .setPath("xources/hello/HelloJava.xoo") + .setRuleRepository("xoo") + .setRuleKey("OneIssuePerLine") + .setLine(1) + .setSeverity(Severity.MAJOR) + .setCreationDate(date("14/03/2004")) + .setChecksum(DigestUtils.md5Hex("packagehello;")) + .setStatus("OPEN") + .build()) + // Existing issue on project that is still detected + .mockServerIssue(ServerIssue.newBuilder().setKey("resolved-on-project") + .setModuleKey("sample") + .setRuleRepository("xoo") + .setRuleKey("OneIssuePerModule") + .setSeverity(Severity.CRITICAL) + .setCreationDate(date("14/03/2004")) + .setStatus("OPEN") + .build()) + // Manual issue + .mockServerIssue(ServerIssue.newBuilder().setKey("manual") + .setModuleKey("sample") + .setPath("xources/hello/HelloJava.xoo") + .setRuleRepository("manual") + .setRuleKey("MyManualIssue") + .setLine(1) + .setSeverity(Severity.MAJOR) + .setCreationDate(date("14/03/2004")) + .setChecksum(DigestUtils.md5Hex("packagehello;")) + .setStatus("OPEN") + .build()) + .build(); + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + + private File copyProject(String path) throws Exception { + File projectDir = temp.newFolder(); + File originalProjectDir = new File(IssueModeAndReportsMediumTest.class.getResource(path).toURI()); + FileUtils.copyDirectory(originalProjectDir, projectDir, FileFilterUtils.notFileFilter(FileFilterUtils.nameFileFilter(".sonar"))); + return projectDir; + } + + @Test + public void testScanAll() throws Exception { + File projectDir = copyProject("/mediumtest/xoo/sample"); + + TaskResult result = tester + .newScanTask(new File(projectDir, "sonar-project.properties")) + .property("sonar.scanAllFiles", "true") + .start(); + + assertNumberIssues(result, 16, 3, 0); + + /* + * 8 new per line + * 1 manual + */ + assertNumberIssuesOnFile(result, "HelloJava.xoo", 9); + } + + @Test + public void testScanOnlyChangedFiles() throws Exception { + File projectDir = copyProject("/mediumtest/xoo/sample"); + + TaskResult result = tester + .newScanTask(new File(projectDir, "sonar-project.properties")) + .start(); + + /* + * We have: + * 6 new issues per line (open) in helloscala.xoo + * 2 new issues per file in helloscala.xoo / ClassOneTest.xoo + * 1 server issue (open, not new) copied from server in HelloJava.xoo (this file is unchanged) + * 1 manual issue (open, not new) in HelloJava.xoo + * 1 existing issue on the project (open, not new) + */ + assertNumberIssues(result, 8, 3, 0); + + // should only have server issues (HelloJava.xoo should not have been analyzed) + assertNumberIssuesOnFile(result, "HelloJava.xoo", 2); + } + + private static void assertNumberIssuesOnFile(TaskResult result, final String fileNameEndsWith, int issues) { + assertThat(result.trackedIssues()).haveExactly(issues, new Condition<TrackedIssue>() { + @Override + public boolean matches(TrackedIssue value) { + return value.componentKey().endsWith(fileNameEndsWith); + } + }); + } + + private static void assertNumberIssues(TaskResult result, int expectedNew, int expectedOpen, int expectedResolved) { + int newIssues = 0; + int openIssues = 0; + int resolvedIssue = 0; + for (TrackedIssue issue : result.trackedIssues()) { + System.out + .println(issue.getMessage() + " " + issue.key() + " " + issue.getRuleKey() + " " + issue.isNew() + " " + issue.resolution() + " " + issue.componentKey() + " " + + issue.startLine()); + if (issue.isNew()) { + newIssues++; + } else if (issue.resolution() != null) { + resolvedIssue++; + } else { + openIssues++; + } + } + + System.out.println("new: " + newIssues + " open: " + openIssues + " resolved " + resolvedIssue); + assertThat(newIssues).isEqualTo(expectedNew); + assertThat(openIssues).isEqualTo(expectedOpen); + assertThat(resolvedIssue).isEqualTo(expectedResolved); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/log/ExceptionHandlingMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/log/ExceptionHandlingMediumTest.java new file mode 100644 index 00000000000..db1a2dcd30e --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/log/ExceptionHandlingMediumTest.java @@ -0,0 +1,116 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.log; + +import java.util.Collections; + +import org.hamcrest.Matchers; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; +import org.junit.BeforeClass; +import org.sonar.batch.bootstrapper.EnvironmentInformation; +import org.sonar.api.utils.MessageException; +import org.apache.commons.lang.mutable.MutableBoolean; +import org.sonar.batch.repository.GlobalRepositoriesLoader; +import org.sonar.scanner.protocol.input.GlobalRepositories; +import org.sonar.batch.bootstrapper.Batch; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class ExceptionHandlingMediumTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Batch batch; + private static ErrorGlobalRepositoriesLoader loader; + + @BeforeClass + public static void beforeClass() { + loader = new ErrorGlobalRepositoriesLoader(); + } + + public void setUp(boolean verbose) { + Batch.Builder builder = Batch.builder() + .setEnableLoggingConfiguration(true) + .addComponents( + loader, + new EnvironmentInformation("mediumTest", "1.0")); + + if (verbose) { + builder.setBootstrapProperties(Collections.singletonMap("sonar.verbose", "true")); + } + batch = builder.build(); + } + + @Test + public void test() throws Exception { + setUp(false); + loader.withCause = false; + thrown.expect(MessageException.class); + thrown.expectMessage("Error loading repository"); + thrown.expectCause(Matchers.nullValue(Throwable.class)); + + batch.start(); + } + + @Test + public void testWithCause() throws Exception { + setUp(false); + loader.withCause = true; + + thrown.expect(MessageException.class); + thrown.expectMessage("Error loading repository"); + thrown.expectCause(new TypeSafeMatcher<Throwable>() { + @Override + public void describeTo(Description description) { + } + + @Override + protected boolean matchesSafely(Throwable item) { + return item instanceof IllegalStateException && item.getMessage().equals("Code 401"); + } + }); + + batch.start(); + } + + @Test + public void testWithVerbose() throws Exception { + setUp(true); + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Unable to load component class"); + batch.start(); + } + + private static class ErrorGlobalRepositoriesLoader implements GlobalRepositoriesLoader { + boolean withCause = false; + + @Override + public GlobalRepositories load(MutableBoolean fromCache) { + if (withCause) { + IllegalStateException cause = new IllegalStateException("Code 401"); + throw MessageException.of("Error loading repository", cause); + } else { + throw MessageException.of("Error loading repository"); + } + } + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/log/LogListenerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/log/LogListenerTest.java new file mode 100644 index 00000000000..f360dca56ac --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/log/LogListenerTest.java @@ -0,0 +1,216 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.log; + +import com.google.common.collect.ImmutableMap; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.io.FileUtils; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.batch.bootstrapper.LogOutput; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.xoo.XooPlugin; +import static org.assertj.core.api.Assertions.assertThat; + +public class LogListenerTest { + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Pattern simpleTimePattern = Pattern.compile("\\d{2}:\\d{2}:\\d{2}"); + private List<LogEvent> logOutput; + private StringBuilder logOutputStr; + private ByteArrayOutputStream stdOutTarget = new ByteArrayOutputStream(); + private ByteArrayOutputStream stdErrTarget = new ByteArrayOutputStream(); + private static PrintStream savedStdOut; + private static PrintStream savedStdErr; + + public BatchMediumTester tester = BatchMediumTester.builder() + .registerPlugin("xoo", new XooPlugin()) + .addDefaultQProfile("xoo", "Sonar Way") + .setLogOutput(new SimpleLogListener()) + .build(); + + private File baseDir; + + private ImmutableMap.Builder<String, String> builder; + + @BeforeClass + public static void backupStdStreams() { + savedStdOut = System.out; + savedStdErr = System.err; + } + + @AfterClass + public static void resumeStdStreams() { + if (savedStdOut != null) { + System.setOut(savedStdOut); + } + if (savedStdErr != null) { + System.setErr(savedStdErr); + } + } + + @Before + public void prepare() throws IOException { + System.setOut(new PrintStream(stdOutTarget)); + System.setErr(new PrintStream(stdErrTarget)); + // logger from the batch might write to it asynchronously + logOutput = Collections.synchronizedList(new LinkedList<LogEvent>()); + logOutputStr = new StringBuilder(); + tester.start(); + + baseDir = temp.getRoot(); + + builder = ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project"); + } + + private void assertNoStdOutput() { + assertThat(stdOutTarget.toByteArray()).isEmpty(); + assertThat(stdErrTarget.toByteArray()).isEmpty(); + } + + /** + * Check that log message is not formatted, i.e. has no log level and timestamp. + */ + private void assertMsgClean(String msg) { + for (LogOutput.Level l : LogOutput.Level.values()) { + assertThat(msg).doesNotContain(l.toString()); + } + + Matcher matcher = simpleTimePattern.matcher(msg); + assertThat(matcher.find()).isFalse(); + } + + @Test + public void testChangeLogForAnalysis() throws IOException, InterruptedException { + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + FileUtils.write(xooFile, "Sample xoo\ncontent"); + + tester.newTask() + .properties(builder + .put("sonar.sources", "src") + .put("sonar.verbose", "true") + .build()) + .start(); + + tester.stop(); + for (LogEvent e : logOutput) { + savedStdOut.println("[captured]" + e.level + " " + e.msg); + } + + // only done in DEBUG during analysis + assertThat(logOutputStr.toString()).contains("Post-jobs : "); + } + + @Test + public void testNoStdLog() throws IOException { + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + FileUtils.write(xooFile, "Sample xoo\ncontent"); + + tester.newTask() + .properties(builder + .put("sonar.sources", "src") + .build()) + .start(); + tester.stop(); + + assertNoStdOutput(); + assertThat(logOutput).isNotEmpty(); + + synchronized (logOutput) { + for (LogEvent e : logOutput) { + savedStdOut.println("[captured]" + e.level + " " + e.msg); + } + } + } + + @Test + public void testNoFormattedMsgs() throws IOException { + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + FileUtils.write(xooFile, "Sample xoo\ncontent"); + + tester.newTask() + .properties(builder + .put("sonar.sources", "src") + .build()) + .start(); + tester.stop(); + + assertNoStdOutput(); + + synchronized (logOutput) { + for (LogEvent e : logOutput) { + assertMsgClean(e.msg); + savedStdOut.println("[captured]" + e.level + " " + e.msg); + } + } + } + + private class SimpleLogListener implements LogOutput { + @Override + public void log(String msg, Level level) { + logOutput.add(new LogEvent(msg, level)); + logOutputStr.append(msg).append("\n"); + } + } + + private static class LogEvent { + String msg; + LogOutput.Level level; + + LogEvent(String msg, LogOutput.Level level) { + this.msg = msg; + this.level = level; + } + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/measures/MeasuresMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/measures/MeasuresMediumTest.java new file mode 100644 index 00000000000..e9db30c49f4 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/measures/MeasuresMediumTest.java @@ -0,0 +1,131 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.measures; + +import com.google.common.collect.ImmutableMap; +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import org.apache.commons.io.FileUtils; +import org.assertj.core.groups.Tuple; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.TaskResult; +import org.sonar.scanner.protocol.output.ScannerReport.Measure; +import org.sonar.xoo.XooPlugin; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; + +public class MeasuresMediumTest { + + @org.junit.Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private File baseDir; + private File srcDir; + + public BatchMediumTester tester = BatchMediumTester.builder() + .registerPlugin("xoo", new XooPlugin()) + .addDefaultQProfile("xoo", "Sonar Way") + .build(); + + @Before + public void prepare() { + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + + @Before + public void setUp() { + baseDir = temp.getRoot(); + srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + } + + @Test + public void computeMeasuresOnTempProject() throws IOException { + File xooFile = new File(srcDir, "sample.xoo"); + File xooMeasureFile = new File(srcDir, "sample.xoo.measures"); + FileUtils.write(xooFile, "Sample xoo\ncontent"); + FileUtils.write(xooMeasureFile, "lines:20"); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .put("sonar.cpd.xoo.skip", "true") + .build()) + .start(); + + Map<String, List<Measure>> allMeasures = result.allMeasures(); + + assertThat(allMeasures.get("com.foo.project")).extracting("metricKey", "intValue", "doubleValue", "stringValue").containsOnly( + Tuple.tuple(CoreMetrics.QUALITY_PROFILES_KEY, 0, 0.0, + "[{\"key\":\"Sonar Way\",\"language\":\"xoo\",\"name\":\"Sonar Way\",\"rulesUpdatedAt\":\"2009-02-13T23:31:31+0000\"}]")); + + assertThat(allMeasures.get("com.foo.project:src/sample.xoo")).extracting("metricKey", "intValue").containsOnly( + Tuple.tuple(CoreMetrics.LINES_KEY, 2)); + } + + @Test + public void computeLinesOnAllFiles() throws IOException { + File xooFile = new File(srcDir, "sample.xoo"); + FileUtils.write(xooFile, "Sample xoo\n\ncontent"); + + File otherFile = new File(srcDir, "sample.other"); + FileUtils.write(otherFile, "Sample other\ncontent\n"); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .put("sonar.import_unknown_files", "true") + .build()) + .start(); + + Map<String, List<Measure>> allMeasures = result.allMeasures(); + + assertThat(allMeasures.get("com.foo.project:src/sample.xoo")).extracting("metricKey", "intValue") + .contains(tuple("lines", 3)); + assertThat(allMeasures.get("com.foo.project:src/sample.other")).extracting("metricKey", "intValue") + .contains(tuple("lines", 3), tuple("ncloc", 2)); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/scm/ScmMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/scm/ScmMediumTest.java new file mode 100644 index 00000000000..37c8da1ef01 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/scm/ScmMediumTest.java @@ -0,0 +1,363 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.scm; + +import com.google.common.collect.ImmutableMap; +import java.io.File; +import java.io.IOException; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.utils.PathUtils; +import org.sonar.api.utils.log.LogTester; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.BatchMediumTester.TaskBuilder; +import org.sonar.batch.repository.FileData; +import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.scanner.protocol.output.ScannerReportReader; +import org.sonar.scanner.protocol.output.ScannerReport.Component; +import org.sonar.scanner.protocol.output.ScannerReport.Changesets.Changeset; +import org.sonar.xoo.XooPlugin; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ScmMediumTest { + + private static final String MISSING_BLAME_INFORMATION_FOR_THE_FOLLOWING_FILES = "Missing blame information for the following files:"; + private static final String CHANGED_CONTENT_SCM_ON_SERVER_XOO = "src/changed_content_scm_on_server.xoo"; + private static final String SAME_CONTENT_SCM_ON_SERVER_XOO = "src/same_content_scm_on_server.xoo"; + private static final String SAME_CONTENT_NO_SCM_ON_SERVER_XOO = "src/same_content_no_scm_on_server.xoo"; + private static final String SAMPLE_XOO_CONTENT = "Sample xoo\ncontent"; + + @org.junit.Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + public LogTester logTester = new LogTester(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .registerPlugin("xoo", new XooPlugin()) + .addDefaultQProfile("xoo", "Sonar Way") + .addFileData("com.foo.project", CHANGED_CONTENT_SCM_ON_SERVER_XOO, new FileData(DigestUtils.md5Hex(SAMPLE_XOO_CONTENT), null)) + .addFileData("com.foo.project", SAME_CONTENT_NO_SCM_ON_SERVER_XOO, new FileData(DigestUtils.md5Hex(SAMPLE_XOO_CONTENT), null)) + .addFileData("com.foo.project", SAME_CONTENT_SCM_ON_SERVER_XOO, new FileData(DigestUtils.md5Hex(SAMPLE_XOO_CONTENT), "1.1")) + .build(); + + @Before + public void prepare() { + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + + @Test + public void testScmMeasure() throws IOException { + + File baseDir = prepareProject(); + + tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .put("sonar.scm.provider", "xoo") + .build()) + .start(); + + ScannerReport.Changesets fileScm = getChangesets(baseDir, "src/sample.xoo"); + + assertThat(fileScm.getChangesetIndexByLineList()).hasSize(5); + + Changeset changesetLine1 = fileScm.getChangeset(fileScm.getChangesetIndexByLine(0)); + assertThat(changesetLine1.hasAuthor()).isFalse(); + + Changeset changesetLine2 = fileScm.getChangeset(fileScm.getChangesetIndexByLine(1)); + assertThat(changesetLine2.getAuthor()).isEqualTo("julien"); + + Changeset changesetLine3 = fileScm.getChangeset(fileScm.getChangesetIndexByLine(2)); + assertThat(changesetLine3.getAuthor()).isEqualTo("julien"); + + Changeset changesetLine4 = fileScm.getChangeset(fileScm.getChangesetIndexByLine(3)); + assertThat(changesetLine4.getAuthor()).isEqualTo("julien"); + + Changeset changesetLine5 = fileScm.getChangeset(fileScm.getChangesetIndexByLine(4)); + assertThat(changesetLine5.getAuthor()).isEqualTo("simon"); + } + + private ScannerReport.Changesets getChangesets(File baseDir, String path) { + File reportDir = new File(baseDir, ".sonar/batch-report"); + ScannerReportReader reader = new ScannerReportReader(reportDir); + + Component project = reader.readComponent(reader.readMetadata().getRootComponentRef()); + Component dir = reader.readComponent(project.getChildRef(0)); + for (Integer fileRef : dir.getChildRefList()) { + Component file = reader.readComponent(fileRef); + if (file.getPath().equals(path)) { + return reader.readChangesets(file.getRef()); + } + } + return null; + } + + @Test + public void noScmOnEmptyFile() throws IOException { + + File baseDir = prepareProject(); + + // Clear file content + FileUtils.write(new File(baseDir, "src/sample.xoo"), ""); + + tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .put("sonar.scm.provider", "xoo") + .build()) + .start(); + + ScannerReport.Changesets changesets = getChangesets(baseDir, "src/sample.xoo"); + + assertThat(changesets).isNull(); + } + + @Test + public void log_files_with_missing_blame() throws IOException { + + File baseDir = prepareProject(); + File xooFileWithoutBlame = new File(baseDir, "src/sample_no_blame.xoo"); + FileUtils.write(xooFileWithoutBlame, "Sample xoo\ncontent\n3\n4\n5"); + + tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .put("sonar.scm.provider", "xoo") + .build()) + .start(); + + ScannerReport.Changesets file1Scm = getChangesets(baseDir, "src/sample.xoo"); + assertThat(file1Scm).isNotNull(); + + ScannerReport.Changesets fileWithoutBlameScm = getChangesets(baseDir, "src/sample_no_blame.xoo"); + assertThat(fileWithoutBlameScm).isNull(); + + assertThat(logTester.logs()).containsSubsequence("2 files to be analyzed", "1/2 files analyzed", MISSING_BLAME_INFORMATION_FOR_THE_FOLLOWING_FILES, + " * " + PathUtils.sanitize(xooFileWithoutBlame.toPath().toString())); + } + + // SONAR-6397 + @Test + public void optimize_blame() throws IOException { + + File baseDir = prepareProject(); + File changedContentScmOnServer = new File(baseDir, CHANGED_CONTENT_SCM_ON_SERVER_XOO); + FileUtils.write(changedContentScmOnServer, SAMPLE_XOO_CONTENT + "\nchanged"); + File xooScmFile = new File(baseDir, CHANGED_CONTENT_SCM_ON_SERVER_XOO + ".scm"); + FileUtils.write(xooScmFile, + // revision,author,dateTime + "1,foo,2013-01-04\n" + + "1,bar,2013-01-04\n" + + "2,biz,2014-01-04\n"); + + File sameContentScmOnServer = new File(baseDir, SAME_CONTENT_SCM_ON_SERVER_XOO); + FileUtils.write(sameContentScmOnServer, SAMPLE_XOO_CONTENT); + // No need to write .scm file since this file should not be blamed + + File sameContentNoScmOnServer = new File(baseDir, SAME_CONTENT_NO_SCM_ON_SERVER_XOO); + FileUtils.write(sameContentNoScmOnServer, SAMPLE_XOO_CONTENT); + xooScmFile = new File(baseDir, SAME_CONTENT_NO_SCM_ON_SERVER_XOO + ".scm"); + FileUtils.write(xooScmFile, + // revision,author,dateTime + "1,foo,2013-01-04\n" + + "1,bar,2013-01-04\n"); + + tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .put("sonar.scm.provider", "xoo") + .build()) + .start(); + + assertThat(getChangesets(baseDir, "src/sample.xoo")).isNotNull(); + + assertThat(getChangesets(baseDir, CHANGED_CONTENT_SCM_ON_SERVER_XOO)).isNotNull(); + + assertThat(getChangesets(baseDir, SAME_CONTENT_SCM_ON_SERVER_XOO)).isNull(); + + assertThat(getChangesets(baseDir, SAME_CONTENT_NO_SCM_ON_SERVER_XOO)).isNotNull(); + + assertThat(logTester.logs()).containsSubsequence("3 files to be analyzed", "3/3 files analyzed"); + assertThat(logTester.logs()).doesNotContain(MISSING_BLAME_INFORMATION_FOR_THE_FOLLOWING_FILES); + } + + @Test + public void forceReload() throws IOException { + + File baseDir = prepareProject(); + File xooFileNoScm = new File(baseDir, SAME_CONTENT_SCM_ON_SERVER_XOO); + FileUtils.write(xooFileNoScm, SAMPLE_XOO_CONTENT); + File xooScmFile = new File(baseDir, SAME_CONTENT_SCM_ON_SERVER_XOO + ".scm"); + FileUtils.write(xooScmFile, + // revision,author,dateTime + "1,foo,2013-01-04\n" + + "1,bar,2013-01-04\n"); + + TaskBuilder taskBuilder = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .put("sonar.scm.provider", "xoo") + // Force reload + .put("sonar.scm.forceReloadAll", "true") + .build()); + + taskBuilder.start(); + + ScannerReport.Changesets file1Scm = getChangesets(baseDir, "src/sample.xoo"); + assertThat(file1Scm).isNotNull(); + + ScannerReport.Changesets file2Scm = getChangesets(baseDir, SAME_CONTENT_SCM_ON_SERVER_XOO); + assertThat(file2Scm).isNotNull(); + } + + @Test + public void configureUsingScmURL() throws IOException { + + File baseDir = prepareProject(); + + tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .put("sonar.links.scm_dev", "scm:xoo:foobar") + .build()) + .start(); + + ScannerReport.Changesets file1Scm = getChangesets(baseDir, "src/sample.xoo"); + assertThat(file1Scm).isNotNull(); + } + + @Test + public void testAutoDetection() throws IOException { + + File baseDir = prepareProject(); + new File(baseDir, ".xoo").createNewFile(); + + tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .build()) + .start(); + + ScannerReport.Changesets file1Scm = getChangesets(baseDir, "src/sample.xoo"); + assertThat(file1Scm).isNotNull(); + } + + private File prepareProject() throws IOException { + File baseDir = temp.getRoot(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile1 = new File(srcDir, "sample.xoo"); + FileUtils.write(xooFile1, "Sample xoo\ncontent\n3\n4\n5"); + File xooScmFile1 = new File(srcDir, "sample.xoo.scm"); + FileUtils.write(xooScmFile1, + // revision,author,dateTime + "1,,2013-01-04\n" + + "2,julien,2013-01-04\n" + + "3,julien,2013-02-03\n" + + "3,julien,2013-02-03\n" + + "4,simon,2013-03-04\n"); + + return baseDir; + } + + @Test + public void testDisableScmSensor() throws IOException { + + File baseDir = prepareProject(); + + tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .put("sonar.scm.disabled", "true") + .put("sonar.scm.provider", "xoo") + .put("sonar.cpd.xoo.skip", "true") + .build()) + .start(); + + ScannerReport.Changesets changesets = getChangesets(baseDir, "src/sample.xoo"); + assertThat(changesets).isNull(); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/symbol/SymbolMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/symbol/SymbolMediumTest.java new file mode 100644 index 00000000000..8d9836f994d --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/symbol/SymbolMediumTest.java @@ -0,0 +1,116 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.symbol; + +import com.google.common.collect.ImmutableMap; +import java.io.File; +import java.io.IOException; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.TaskResult; +import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.xoo.XooPlugin; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SymbolMediumTest { + + @org.junit.Rule + public TemporaryFolder temp = new TemporaryFolder(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .registerPlugin("xoo", new XooPlugin()) + .addDefaultQProfile("xoo", "Sonar Way") + .build(); + + @Before + public void prepare() { + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + + @Test + public void computeSymbolReferencesOnTempProject() throws IOException { + + File baseDir = temp.getRoot(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + File xooSymbolFile = new File(srcDir, "sample.xoo.symbol"); + FileUtils.write(xooFile, "Sample xoo\ncontent\nanother xoo"); + // Highlight xoo symbol + FileUtils.write(xooSymbolFile, "7:10,27"); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .build()) + .start(); + + InputFile file = result.inputFile("src/sample.xoo"); + assertThat(result.symbolReferencesFor(file, 1, 7)).containsOnly(ScannerReport.TextRange.newBuilder().setStartLine(3).setStartOffset(8).setEndLine(3).setEndOffset(11).build()); + } + + @Test + public void computeSymbolReferencesWithVariableLength() throws IOException { + + File baseDir = temp.getRoot(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + File xooSymbolFile = new File(srcDir, "sample.xoo.symbol"); + FileUtils.write(xooFile, "Sample xoo\ncontent\nanother xoo\nyet another"); + // Highlight xoo symbol + FileUtils.write(xooSymbolFile, "7:10,27:32"); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .build()) + .start(); + + InputFile file = result.inputFile("src/sample.xoo"); + assertThat(result.symbolReferencesFor(file, 1, 7)).containsOnly(ScannerReport.TextRange.newBuilder().setStartLine(3).setStartOffset(8).setEndLine(4).setEndOffset(1).build()); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/tasks/TasksMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/tasks/TasksMediumTest.java new file mode 100644 index 00000000000..766e9371898 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/tasks/TasksMediumTest.java @@ -0,0 +1,160 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.tasks; + +import com.google.common.collect.ImmutableMap; +import java.util.Arrays; +import java.util.List; +import org.assertj.core.api.Condition; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.SonarPlugin; +import org.sonar.api.issue.action.Actions; +import org.sonar.api.task.Task; +import org.sonar.api.task.TaskDefinition; +import org.sonar.api.utils.MessageException; +import org.sonar.api.utils.log.LogTester; +import org.sonar.batch.bootstrap.MockHttpServer; +import org.sonar.batch.mediumtest.BatchMediumTester; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TasksMediumTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + public LogTester logTester = new LogTester(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .registerPlugin("faketask", new FakeTaskPlugin()) + .build(); + + private MockHttpServer server = null; + + @After + public void stopServer() { + if (server != null) { + server.stop(); + } + } + + @After + public void stop() { + tester.stop(); + } + + @Test + public void listTasksIncludingBroken() throws Exception { + tester.start(); + tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "list").build()) + .start(); + + assertThat(logTester.logs()).haveExactly(1, new Condition<String>() { + + @Override + public boolean matches(String value) { + return value.contains("Available tasks:") && value.contains("fake: Fake description") && value.contains("broken: Broken description"); + } + }); + } + + @Test + public void runBroken() throws Exception { + tester.start(); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage( + "Unable to load component class org.sonar.batch.mediumtest.tasks.TasksMediumTest$BrokenTask"); + + tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "broken").build()) + .start(); + } + + @Test(expected = MessageException.class) + public void unsupportedTask() throws Exception { + tester = BatchMediumTester.builder() + .build(); + tester.start(); + tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "foo").build()) + .start(); + } + + private void startServer(Integer responseStatus, String responseData) throws Exception { + server = new MockHttpServer(); + server.start(); + + if (responseStatus != null) { + server.setMockResponseStatus(responseStatus); + } + if (responseData != null) { + server.setMockResponseData(responseData); + } + } + + private static class FakeTaskPlugin extends SonarPlugin { + + @Override + public List getExtensions() { + return Arrays.asList(FakeTask.DEF, FakeTask.class, BrokenTask.DEF, BrokenTask.class); + } + + } + + private static class FakeTask implements Task { + + public static final TaskDefinition DEF = TaskDefinition.builder().key("fake").description("Fake description").taskClass(FakeTask.class).build(); + + @Override + public void execute() { + // TODO Auto-generated method stub + + } + + } + + private static class BrokenTask implements Task { + + public static final TaskDefinition DEF = TaskDefinition.builder().key("broken").description("Broken description").taskClass(BrokenTask.class).build(); + private final Actions serverSideComponent; + + public BrokenTask(Actions serverSideComponent) { + this.serverSideComponent = serverSideComponent; + } + + @Override + public void execute() { + System.out.println(serverSideComponent.list()); + ; + + } + + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/tests/CoveragePerTestMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/tests/CoveragePerTestMediumTest.java new file mode 100644 index 00000000000..d62d978ecaf --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/tests/CoveragePerTestMediumTest.java @@ -0,0 +1,160 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.tests; + +import org.hamcrest.Description; + +import org.hamcrest.TypeSafeMatcher; +import org.junit.Rule; +import org.junit.rules.ExpectedException; +import com.google.common.collect.ImmutableMap; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.TaskResult; +import org.sonar.xoo.XooPlugin; + +import java.io.File; +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CoveragePerTestMediumTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public ExpectedException exception = ExpectedException.none(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .registerPlugin("xoo", new XooPlugin()) + .addDefaultQProfile("xoo", "Sonar Way") + .build(); + + @Before + public void prepare() { + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + + @Test + // SONAR-6183 + public void invalidCoverage() throws IOException { + File baseDir = createTestFiles(); + File srcDir = new File(baseDir, "src"); + + File coverageFile = new File(srcDir, "sample.xoo.coverage"); + FileUtils.write(coverageFile, "0:2\n"); + + exception.expect(IllegalStateException.class); + exception.expectMessage("Error processing line 1 of file"); + exception.expectCause(new TypeSafeMatcher<Throwable>() { + + @Override + public void describeTo(Description description) { + // nothing to do + } + + @Override + protected boolean matchesSafely(Throwable item) { + return item.getMessage().contains("Line number must be strictly positive"); + } + }); + runTask(baseDir); + + } + + @Test + public void coveragePerTestInReport() throws IOException { + File baseDir = createTestFiles(); + File testDir = new File(baseDir, "test"); + + File xooTestExecutionFile = new File(testDir, "sampleTest.xoo.test"); + FileUtils.write(xooTestExecutionFile, "some test:4:::OK:UNIT\n" + + "another test:10:::OK:UNIT\n" + + "test without coverage:10:::OK:UNIT\n"); + + File xooCoveragePerTestFile = new File(testDir, "sampleTest.xoo.testcoverage"); + FileUtils.write(xooCoveragePerTestFile, "some test;src/sample.xoo,10,11;src/sample2.xoo,1,2\n" + + "another test;src/sample.xoo,10,20\n"); + + TaskResult result = runTask(baseDir); + + InputFile file = result.inputFile("test/sampleTest.xoo"); + org.sonar.scanner.protocol.output.ScannerReport.CoverageDetail someTest = result.coveragePerTestFor(file, "some test"); + assertThat(someTest.getCoveredFileList()).hasSize(2); + assertThat(someTest.getCoveredFile(0).getFileRef()).isGreaterThan(0); + assertThat(someTest.getCoveredFile(0).getCoveredLineList()).containsExactly(10, 11); + assertThat(someTest.getCoveredFile(1).getFileRef()).isGreaterThan(0); + assertThat(someTest.getCoveredFile(1).getCoveredLineList()).containsExactly(1, 2); + + org.sonar.scanner.protocol.output.ScannerReport.CoverageDetail anotherTest = result.coveragePerTestFor(file, "another test"); + assertThat(anotherTest.getCoveredFileList()).hasSize(1); + assertThat(anotherTest.getCoveredFile(0).getFileRef()).isGreaterThan(0); + assertThat(anotherTest.getCoveredFile(0).getCoveredLineList()).containsExactly(10, 20); + } + + private TaskResult runTask(File baseDir) { + return tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .put("sonar.tests", "test") + .build()) + .start(); + } + + private File createTestFiles() throws IOException { + File baseDir = temp.getRoot(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + File testDir = new File(baseDir, "test"); + testDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + FileUtils.write(xooFile, "foo"); + + File xooFile2 = new File(srcDir, "sample2.xoo"); + FileUtils.write(xooFile2, "foo"); + + File xooTestFile = new File(testDir, "sampleTest.xoo"); + FileUtils.write(xooTestFile, "failure\nerror\nok\nskipped"); + + File xooTestFile2 = new File(testDir, "sample2Test.xoo"); + FileUtils.write(xooTestFile2, "test file tests"); + + return baseDir; + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/tests/TestExecutionMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/tests/TestExecutionMediumTest.java new file mode 100644 index 00000000000..cae559f16c5 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/tests/TestExecutionMediumTest.java @@ -0,0 +1,105 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.mediumtest.tests; + +import com.google.common.collect.ImmutableMap; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.batch.mediumtest.BatchMediumTester; +import org.sonar.batch.mediumtest.TaskResult; +import org.sonar.scanner.protocol.Constants.TestStatus; +import org.sonar.xoo.XooPlugin; + +import java.io.File; +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestExecutionMediumTest { + + @org.junit.Rule + public TemporaryFolder temp = new TemporaryFolder(); + + public BatchMediumTester tester = BatchMediumTester.builder() + .registerPlugin("xoo", new XooPlugin()) + .addDefaultQProfile("xoo", "Sonar Way") + .build(); + + @Before + public void prepare() { + tester.start(); + } + + @After + public void stop() { + tester.stop(); + } + + @Test + public void unitTests() throws IOException { + + File baseDir = temp.getRoot(); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + File testDir = new File(baseDir, "test"); + testDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + FileUtils.write(xooFile, "foo"); + + File xooTestFile = new File(testDir, "sampleTest.xoo"); + FileUtils.write(xooTestFile, "failure\nerror\nok\nskipped"); + + File xooTestExecutionFile = new File(testDir, "sampleTest.xoo.test"); + FileUtils.write(xooTestExecutionFile, "skipped::::SKIPPED:UNIT\n" + + "failure:2:Failure::FAILURE:UNIT\n" + + "error:2:Error:The stack:ERROR:UNIT\n" + + "success:4:::OK:INTEGRATION"); + + TaskResult result = tester.newTask() + .properties(ImmutableMap.<String, String>builder() + .put("sonar.task", "scan") + .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) + .put("sonar.projectKey", "com.foo.project") + .put("sonar.projectName", "Foo Project") + .put("sonar.projectVersion", "1.0-SNAPSHOT") + .put("sonar.projectDescription", "Description of Foo Project") + .put("sonar.sources", "src") + .put("sonar.tests", "test") + .build()) + .start(); + + InputFile file = result.inputFile("test/sampleTest.xoo"); + org.sonar.scanner.protocol.output.ScannerReport.Test success = result.testExecutionFor(file, "success"); + assertThat(success.getDurationInMs()).isEqualTo(4); + assertThat(success.getStatus()).isEqualTo(TestStatus.OK); + + org.sonar.scanner.protocol.output.ScannerReport.Test error = result.testExecutionFor(file, "error"); + assertThat(error.getDurationInMs()).isEqualTo(2); + assertThat(error.getStatus()).isEqualTo(TestStatus.ERROR); + assertThat(error.getMsg()).isEqualTo("Error"); + assertThat(error.getStacktrace()).isEqualTo("The stack"); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/phases/PostJobsExecutorTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/phases/PostJobsExecutorTest.java new file mode 100644 index 00000000000..f9fda97b21c --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/phases/PostJobsExecutorTest.java @@ -0,0 +1,60 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.phases; + +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.batch.PostJob; +import org.sonar.api.batch.SensorContext; +import org.sonar.api.resources.Project; +import org.sonar.batch.bootstrap.BatchExtensionDictionnary; +import org.sonar.batch.events.EventBus; + +import java.util.Arrays; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class PostJobsExecutorTest { + PostJobsExecutor executor; + + Project project = new Project("project"); + BatchExtensionDictionnary selector = mock(BatchExtensionDictionnary.class); + PostJob job1 = mock(PostJob.class); + PostJob job2 = mock(PostJob.class); + SensorContext context = mock(SensorContext.class); + + @Before + public void setUp() { + executor = new PostJobsExecutor(selector, project, mock(EventBus.class)); + } + + @Test + public void should_execute_post_jobs() { + when(selector.select(PostJob.class, project, true, null)).thenReturn(Arrays.asList(job1, job2)); + + executor.execute(context); + + verify(job1).executeOn(project, context); + verify(job2).executeOn(project, context); + + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/platform/DefaultServerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/platform/DefaultServerTest.java new file mode 100644 index 00000000000..801848354fc --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/platform/DefaultServerTest.java @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.platform; + +import org.sonar.batch.bootstrap.GlobalProperties; + +import org.junit.Test; +import org.sonar.api.CoreProperties; +import org.sonar.api.config.Settings; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DefaultServerTest { + + @Test + public void shouldLoadServerProperties() { + Settings settings = new Settings(); + settings.setProperty(CoreProperties.SERVER_ID, "123"); + settings.setProperty(CoreProperties.SERVER_VERSION, "2.2"); + settings.setProperty(CoreProperties.SERVER_STARTTIME, "2010-05-18T17:59:00+0000"); + settings.setProperty(CoreProperties.PERMANENT_SERVER_ID, "abcde"); + GlobalProperties props = mock(GlobalProperties.class); + when(props.property("sonar.host.url")).thenReturn("http://foo.com"); + + DefaultServer metadata = new DefaultServer(settings, props); + + assertThat(metadata.getId()).isEqualTo("123"); + assertThat(metadata.getVersion()).isEqualTo("2.2"); + assertThat(metadata.getStartedAt()).isNotNull(); + assertThat(metadata.getURL()).isEqualTo("http://foo.com"); + assertThat(metadata.getPermanentServerId()).isEqualTo("abcde"); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/postjob/DefaultPostJobContextTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/postjob/DefaultPostJobContextTest.java new file mode 100644 index 00000000000..12f00a55d90 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/postjob/DefaultPostJobContextTest.java @@ -0,0 +1,87 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.postjob; + +import org.sonar.batch.issue.tracking.TrackedIssue; + +import java.util.Arrays; + +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.postjob.issue.Issue; +import org.sonar.api.batch.rule.Severity; +import org.sonar.api.config.Settings; +import org.sonar.api.resources.File; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.issue.IssueCache; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DefaultPostJobContextTest { + + private IssueCache issueCache; + private BatchComponentCache resourceCache; + private AnalysisMode analysisMode; + private DefaultPostJobContext context; + private Settings settings; + + @Before + public void prepare() { + issueCache = mock(IssueCache.class); + resourceCache = new BatchComponentCache(); + analysisMode = mock(AnalysisMode.class); + settings = new Settings(); + context = new DefaultPostJobContext(settings, analysisMode, issueCache, resourceCache); + } + + @Test + public void test() { + assertThat(context.settings()).isSameAs(settings); + assertThat(context.analysisMode()).isSameAs(analysisMode); + + TrackedIssue defaultIssue = new TrackedIssue(); + defaultIssue.setComponentKey("foo:src/Foo.php"); + defaultIssue.setGap(2.0); + defaultIssue.setNew(true); + defaultIssue.setKey("xyz"); + defaultIssue.setStartLine(1); + defaultIssue.setMessage("msg"); + defaultIssue.setSeverity("BLOCKER"); + when(issueCache.all()).thenReturn(Arrays.asList(defaultIssue)); + + Issue issue = context.issues().iterator().next(); + assertThat(issue.componentKey()).isEqualTo("foo:src/Foo.php"); + assertThat(issue.effortToFix()).isEqualTo(2.0); + assertThat(issue.isNew()).isTrue(); + assertThat(issue.key()).isEqualTo("xyz"); + assertThat(issue.line()).isEqualTo(1); + assertThat(issue.message()).isEqualTo("msg"); + assertThat(issue.severity()).isEqualTo(Severity.BLOCKER); + assertThat(issue.inputComponent()).isNull(); + + InputFile inputPath = mock(InputFile.class); + resourceCache.add(File.create("src/Foo.php").setEffectiveKey("foo:src/Foo.php"), null).setInputComponent(inputPath); + assertThat(issue.inputComponent()).isEqualTo(inputPath); + + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/postjob/PostJobOptimizerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/postjob/PostJobOptimizerTest.java new file mode 100644 index 00000000000..e5d30f1dfa3 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/postjob/PostJobOptimizerTest.java @@ -0,0 +1,77 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.postjob; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.api.batch.postjob.internal.DefaultPostJobDescriptor; +import org.sonar.api.config.Settings; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class PostJobOptimizerTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private PostJobOptimizer optimizer; + private Settings settings; + private AnalysisMode analysisMode; + + @Before + public void prepare() { + settings = new Settings(); + analysisMode = mock(AnalysisMode.class); + optimizer = new PostJobOptimizer(settings, analysisMode); + } + + @Test + public void should_run_analyzer_with_no_metadata() { + DefaultPostJobDescriptor descriptor = new DefaultPostJobDescriptor(); + + assertThat(optimizer.shouldExecute(descriptor)).isTrue(); + } + + @Test + public void should_optimize_on_settings() { + DefaultPostJobDescriptor descriptor = new DefaultPostJobDescriptor() + .requireProperty("sonar.foo.reportPath"); + assertThat(optimizer.shouldExecute(descriptor)).isFalse(); + + settings.setProperty("sonar.foo.reportPath", "foo"); + assertThat(optimizer.shouldExecute(descriptor)).isTrue(); + } + + @Test + public void should_disabled_in_issues_mode() { + DefaultPostJobDescriptor descriptor = new DefaultPostJobDescriptor() + .disabledInIssues(); + assertThat(optimizer.shouldExecute(descriptor)).isTrue(); + + when(analysisMode.isIssues()).thenReturn(true); + + assertThat(optimizer.shouldExecute(descriptor)).isFalse(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/profiling/PhasesSumUpTimeProfilerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/profiling/PhasesSumUpTimeProfilerTest.java new file mode 100644 index 00000000000..9c8e4ed8d56 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/profiling/PhasesSumUpTimeProfilerTest.java @@ -0,0 +1,410 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.profiling; + +import com.google.common.collect.Maps; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.Decorator; +import org.sonar.api.batch.Initializer; +import org.sonar.api.batch.PostJob; +import org.sonar.api.batch.Sensor; +import org.sonar.api.batch.SensorContext; +import org.sonar.api.batch.events.DecoratorExecutionHandler; +import org.sonar.api.batch.events.DecoratorsPhaseHandler; +import org.sonar.api.batch.events.InitializerExecutionHandler; +import org.sonar.api.batch.events.InitializersPhaseHandler; +import org.sonar.api.batch.events.PostJobExecutionHandler; +import org.sonar.api.batch.events.PostJobsPhaseHandler; +import org.sonar.api.batch.events.ProjectAnalysisHandler; +import org.sonar.api.batch.events.ProjectAnalysisHandler.ProjectAnalysisEvent; +import org.sonar.api.batch.events.SensorExecutionHandler; +import org.sonar.api.batch.events.SensorExecutionHandler.SensorExecutionEvent; +import org.sonar.api.batch.events.SensorsPhaseHandler; +import org.sonar.api.batch.events.SensorsPhaseHandler.SensorsPhaseEvent; +import org.sonar.api.resources.Project; +import org.sonar.api.utils.System2; +import org.sonar.batch.bootstrap.GlobalProperties; +import org.sonar.batch.events.BatchStepEvent; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +public class PhasesSumUpTimeProfilerTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private MockedSystem clock; + private PhasesSumUpTimeProfiler profiler; + + @Before + public void prepare() throws Exception { + clock = new MockedSystem(); + Map<String, String> props = Maps.newHashMap(); + props.put(CoreProperties.WORKING_DIRECTORY, temp.newFolder().getAbsolutePath()); + profiler = new PhasesSumUpTimeProfiler(clock, new GlobalProperties(props)); + } + + @Test + public void testSimpleProject() throws InterruptedException { + + final Project project = mockProject("my:project", true); + when(project.getModules()).thenReturn(Collections.<Project>emptyList()); + + fakeAnalysis(profiler, project); + + assertThat(profiler.currentModuleProfiling.getProfilingPerPhase(Phase.INIT).getProfilingPerItem(new FakeInitializer()).totalTime()).isEqualTo(7L); + assertThat(profiler.currentModuleProfiling.getProfilingPerPhase(Phase.SENSOR).getProfilingPerItem(new FakeSensor()).totalTime()).isEqualTo(10L); + assertThat(profiler.currentModuleProfiling.getProfilingPerPhase(Phase.POSTJOB).getProfilingPerItem(new FakePostJob()).totalTime()).isEqualTo(30L); + assertThat(profiler.currentModuleProfiling.getProfilingPerBatchStep("Free memory").totalTime()).isEqualTo(9L); + } + + @Test + public void testMultimoduleProject() throws InterruptedException { + final Project project = mockProject("project root", true); + final Project moduleA = mockProject("moduleA", false); + final Project moduleB = mockProject("moduleB", false); + when(project.getModules()).thenReturn(Arrays.asList(moduleA, moduleB)); + + fakeAnalysis(profiler, moduleA); + fakeAnalysis(profiler, moduleB); + fakeAnalysis(profiler, project); + + assertThat(profiler.currentModuleProfiling.getProfilingPerPhase(Phase.INIT).getProfilingPerItem(new FakeInitializer()).totalTime()).isEqualTo(7L); + assertThat(profiler.currentModuleProfiling.getProfilingPerPhase(Phase.SENSOR).getProfilingPerItem(new FakeSensor()).totalTime()).isEqualTo(10L); + assertThat(profiler.currentModuleProfiling.getProfilingPerPhase(Phase.POSTJOB).getProfilingPerItem(new FakePostJob()).totalTime()).isEqualTo(30L); + + assertThat(profiler.totalProfiling.getProfilingPerPhase(Phase.INIT).getProfilingPerItem(new FakeInitializer()).totalTime()).isEqualTo(21L); + assertThat(profiler.totalProfiling.getProfilingPerPhase(Phase.SENSOR).getProfilingPerItem(new FakeSensor()).totalTime()).isEqualTo(30L); + assertThat(profiler.totalProfiling.getProfilingPerPhase(Phase.POSTJOB).getProfilingPerItem(new FakePostJob()).totalTime()).isEqualTo(90L); + } + + @Test + public void testDisplayTimings() { + AbstractTimeProfiling profiling = new AbstractTimeProfiling(System2.INSTANCE) { + }; + + profiling.setTotalTime(5); + assertThat(profiling.totalTimeAsString()).isEqualTo("5ms"); + + profiling.setTotalTime(5 * 1000 + 12); + assertThat(profiling.totalTimeAsString()).isEqualTo("5s"); + + profiling.setTotalTime(5 * 60 * 1000 + 12 * 1000); + assertThat(profiling.totalTimeAsString()).isEqualTo("5min 12s"); + + profiling.setTotalTime(5 * 60 * 1000); + assertThat(profiling.totalTimeAsString()).isEqualTo("5min"); + } + + private class MockedSystem extends System2 { + private long now = 0; + + @Override + public long now() { + return now; + } + + public void sleep(long duration) { + now += duration; + } + } + + private Project mockProject(String name, boolean isRoot) { + final Project project = spy(new Project("myProject")); + when(project.isRoot()).thenReturn(isRoot); + when(project.getName()).thenReturn(name); + return project; + } + + private void fakeAnalysis(PhasesSumUpTimeProfiler profiler, final Project module) { + // Start of moduleA + profiler.onProjectAnalysis(projectEvent(module, true)); + initializerPhase(profiler); + sensorPhase(profiler); + postJobPhase(profiler); + batchStep(profiler); + // End of moduleA + profiler.onProjectAnalysis(projectEvent(module, false)); + } + + private void batchStep(PhasesSumUpTimeProfiler profiler) { + // Start of batch step + profiler.onBatchStep(new BatchStepEvent("Free memory", true)); + clock.sleep(9); + // End of batch step + profiler.onBatchStep(new BatchStepEvent("Free memory", false)); + } + + private void initializerPhase(PhasesSumUpTimeProfiler profiler) { + Initializer initializer = new FakeInitializer(); + // Start of initializer phase + profiler.onInitializersPhase(initializersEvent(true)); + // Start of an initializer + profiler.onInitializerExecution(initializerEvent(initializer, true)); + clock.sleep(7); + // End of an initializer + profiler.onInitializerExecution(initializerEvent(initializer, false)); + // End of initializer phase + profiler.onInitializersPhase(initializersEvent(false)); + } + + private void sensorPhase(PhasesSumUpTimeProfiler profiler) { + Sensor sensor = new FakeSensor(); + // Start of sensor phase + profiler.onSensorsPhase(sensorsEvent(true)); + // Start of a Sensor + profiler.onSensorExecution(sensorEvent(sensor, true)); + clock.sleep(10); + // End of a Sensor + profiler.onSensorExecution(sensorEvent(sensor, false)); + // End of sensor phase + profiler.onSensorsPhase(sensorsEvent(false)); + } + + private void postJobPhase(PhasesSumUpTimeProfiler profiler) { + PostJob postJob = new FakePostJob(); + // Start of sensor phase + profiler.onPostJobsPhase(postJobsEvent(true)); + // Start of a Sensor + profiler.onPostJobExecution(postJobEvent(postJob, true)); + clock.sleep(30); + // End of a Sensor + profiler.onPostJobExecution(postJobEvent(postJob, false)); + // End of sensor phase + profiler.onPostJobsPhase(postJobsEvent(false)); + } + + private SensorExecutionEvent sensorEvent(final Sensor sensor, final boolean start) { + return new SensorExecutionHandler.SensorExecutionEvent() { + + @Override + public boolean isStart() { + return start; + } + + @Override + public boolean isEnd() { + return !start; + } + + @Override + public Sensor getSensor() { + return sensor; + } + }; + } + + private InitializerExecutionHandler.InitializerExecutionEvent initializerEvent(final Initializer initializer, final boolean start) { + return new InitializerExecutionHandler.InitializerExecutionEvent() { + + @Override + public boolean isStart() { + return start; + } + + @Override + public boolean isEnd() { + return !start; + } + + @Override + public Initializer getInitializer() { + return initializer; + } + }; + } + + private DecoratorExecutionHandler.DecoratorExecutionEvent decoratorEvent(final Decorator decorator, final boolean start) { + return new DecoratorExecutionHandler.DecoratorExecutionEvent() { + + @Override + public boolean isStart() { + return start; + } + + @Override + public boolean isEnd() { + return !start; + } + + @Override + public Decorator getDecorator() { + return decorator; + } + }; + } + + private PostJobExecutionHandler.PostJobExecutionEvent postJobEvent(final PostJob postJob, final boolean start) { + return new PostJobExecutionHandler.PostJobExecutionEvent() { + + @Override + public boolean isStart() { + return start; + } + + @Override + public boolean isEnd() { + return !start; + } + + @Override + public PostJob getPostJob() { + return postJob; + } + }; + } + + private SensorsPhaseEvent sensorsEvent(final boolean start) { + return new SensorsPhaseHandler.SensorsPhaseEvent() { + + @Override + public boolean isStart() { + return start; + } + + @Override + public boolean isEnd() { + return !start; + } + + @Override + public List<Sensor> getSensors() { + return null; + } + }; + } + + private InitializersPhaseHandler.InitializersPhaseEvent initializersEvent(final boolean start) { + return new InitializersPhaseHandler.InitializersPhaseEvent() { + + @Override + public boolean isStart() { + return start; + } + + @Override + public boolean isEnd() { + return !start; + } + + @Override + public List<Initializer> getInitializers() { + return null; + } + }; + } + + private PostJobsPhaseHandler.PostJobsPhaseEvent postJobsEvent(final boolean start) { + return new PostJobsPhaseHandler.PostJobsPhaseEvent() { + + @Override + public boolean isStart() { + return start; + } + + @Override + public boolean isEnd() { + return !start; + } + + @Override + public List<PostJob> getPostJobs() { + return null; + } + }; + } + + private DecoratorsPhaseHandler.DecoratorsPhaseEvent decoratorsEvent(final boolean start) { + return new DecoratorsPhaseHandler.DecoratorsPhaseEvent() { + + @Override + public boolean isStart() { + return start; + } + + @Override + public boolean isEnd() { + return !start; + } + + @Override + public List<Decorator> getDecorators() { + return null; + } + }; + } + + private ProjectAnalysisEvent projectEvent(final Project project, final boolean start) { + return new ProjectAnalysisHandler.ProjectAnalysisEvent() { + @Override + public boolean isStart() { + return start; + } + + @Override + public boolean isEnd() { + return !start; + } + + @Override + public Project getProject() { + return project; + } + }; + } + + public class FakeSensor implements Sensor { + @Override + public void analyse(Project project, SensorContext context) { + } + + @Override + public boolean shouldExecuteOnProject(Project project) { + return true; + } + } + + public class FakeInitializer extends Initializer { + @Override + public void execute(Project project) { + } + + @Override + public boolean shouldExecuteOnProject(Project project) { + return true; + } + } + + public class FakePostJob implements PostJob { + @Override + public void executeOn(Project project, SensorContext context) { + } + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/report/ActiveRulesPublisherTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/report/ActiveRulesPublisherTest.java new file mode 100644 index 00000000000..4b912b22d7b --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/report/ActiveRulesPublisherTest.java @@ -0,0 +1,70 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.report; + +import java.io.File; +import java.util.Arrays; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.batch.rule.Severity; +import org.sonar.api.batch.rule.internal.ActiveRulesBuilder; +import org.sonar.api.batch.rule.internal.DefaultActiveRules; +import org.sonar.api.batch.rule.internal.NewActiveRule; +import org.sonar.api.rule.RuleKey; +import org.sonar.core.util.CloseableIterator; +import org.sonar.scanner.protocol.Constants; +import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.scanner.protocol.output.ScannerReportReader; +import org.sonar.scanner.protocol.output.ScannerReportWriter; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ActiveRulesPublisherTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void write() throws Exception { + File outputDir = temp.newFolder(); + ScannerReportWriter writer = new ScannerReportWriter(outputDir); + + NewActiveRule ar = new ActiveRulesBuilder().create(RuleKey.of("java", "S001")).setSeverity("BLOCKER").setParam("p1", "v1"); + ActiveRules activeRules = new DefaultActiveRules(Arrays.asList(ar)); + + ActiveRulesPublisher underTest = new ActiveRulesPublisher(activeRules); + underTest.publish(writer); + + ScannerReportReader reader = new ScannerReportReader(outputDir); + try (CloseableIterator<ScannerReport.ActiveRule> readIt = reader.readActiveRules()) { + ScannerReport.ActiveRule reportAr = readIt.next(); + assertThat(reportAr.getRuleRepository()).isEqualTo("java"); + assertThat(reportAr.getRuleKey()).isEqualTo("S001"); + assertThat(reportAr.getSeverity()).isEqualTo(Constants.Severity.BLOCKER); + assertThat(reportAr.getParamCount()).isEqualTo(1); + assertThat(reportAr.getParam(0).getKey()).isEqualTo("p1"); + assertThat(reportAr.getParam(0).getValue()).isEqualTo("v1"); + + assertThat(readIt.hasNext()).isFalse(); + } + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/report/AnalysisContextReportPublisherTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/report/AnalysisContextReportPublisherTest.java new file mode 100644 index 00000000000..320941119b3 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/report/AnalysisContextReportPublisherTest.java @@ -0,0 +1,207 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.report; + +import com.google.common.collect.ImmutableMap; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; +import org.sonar.batch.bootstrap.BatchPluginRepository; +import org.sonar.batch.repository.ProjectRepositories; +import org.sonar.core.platform.PluginInfo; +import org.sonar.scanner.protocol.output.ScannerReportWriter; +import org.sonar.updatecenter.common.Version; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +public class AnalysisContextReportPublisherTest { + + private static final String BIZ = "BIZ"; + private static final String FOO = "FOO"; + private static final String SONAR_SKIP = "sonar.skip"; + private static final String COM_FOO = "com.foo"; + + @Rule + public LogTester logTester = new LogTester(); + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private BatchPluginRepository pluginRepo = mock(BatchPluginRepository.class); + private AnalysisContextReportPublisher publisher; + private AnalysisMode analysisMode = mock(AnalysisMode.class); + private System2 system2; + private ProjectRepositories projectRepos; + + @Before + public void prepare() throws Exception { + logTester.setLevel(LoggerLevel.INFO); + system2 = mock(System2.class); + when(system2.properties()).thenReturn(new Properties()); + projectRepos = mock(ProjectRepositories.class); + publisher = new AnalysisContextReportPublisher(analysisMode, pluginRepo, system2, projectRepos); + } + + @Test + public void shouldOnlyDumpPluginsByDefault() throws Exception { + when(pluginRepo.getPluginInfos()).thenReturn(Arrays.asList(new PluginInfo("xoo").setName("Xoo").setVersion(Version.create("1.0")))); + + ScannerReportWriter writer = new ScannerReportWriter(temp.newFolder()); + publisher.init(writer); + + assertThat(writer.getFileStructure().analysisLog()).exists(); + assertThat(FileUtils.readFileToString(writer.getFileStructure().analysisLog())).contains("Xoo 1.0 (xoo)"); + + verifyZeroInteractions(system2); + } + + @Test + public void shouldNotDumpInIssuesMode() throws Exception { + when(analysisMode.isIssues()).thenReturn(true); + + ScannerReportWriter writer = new ScannerReportWriter(temp.newFolder()); + publisher.init(writer); + publisher.dumpSettings(ProjectDefinition.create().setProperty("sonar.projectKey", "foo")); + + assertThat(writer.getFileStructure().analysisLog()).doesNotExist(); + } + + @Test + public void dumpServerSideProps() throws Exception { + logTester.setLevel(LoggerLevel.DEBUG); + ScannerReportWriter writer = new ScannerReportWriter(temp.newFolder()); + publisher.init(writer); + + when(projectRepos.moduleExists("foo")).thenReturn(true); + when(projectRepos.settings("foo")).thenReturn(ImmutableMap.of(COM_FOO, "bar", SONAR_SKIP, "true")); + + publisher.dumpSettings(ProjectDefinition.create() + .setProperty("sonar.projectKey", "foo")); + + String content = FileUtils.readFileToString(writer.getFileStructure().analysisLog()); + assertThat(content).doesNotContain(COM_FOO); + assertThat(content).containsOnlyOnce(SONAR_SKIP); + } + + @Test + public void shouldNotDumpSQPropsInSystemProps() throws Exception { + logTester.setLevel(LoggerLevel.DEBUG); + ScannerReportWriter writer = new ScannerReportWriter(temp.newFolder()); + Properties props = new Properties(); + props.setProperty(COM_FOO, "bar"); + props.setProperty(SONAR_SKIP, "true"); + when(system2.properties()).thenReturn(props); + publisher.init(writer); + + String content = FileUtils.readFileToString(writer.getFileStructure().analysisLog()); + assertThat(content).containsOnlyOnce(COM_FOO); + assertThat(content).doesNotContain(SONAR_SKIP); + + publisher.dumpSettings(ProjectDefinition.create() + .setProperty("sonar.projectKey", "foo") + .setProperty(COM_FOO, "bar") + .setProperty(SONAR_SKIP, "true")); + + content = FileUtils.readFileToString(writer.getFileStructure().analysisLog()); + assertThat(content).containsOnlyOnce(COM_FOO); + assertThat(content).containsOnlyOnce(SONAR_SKIP); + } + + @Test + public void shouldNotDumpEnvTwice() throws Exception { + logTester.setLevel(LoggerLevel.DEBUG); + ScannerReportWriter writer = new ScannerReportWriter(temp.newFolder()); + + Map<String, String> env = new HashMap<>(); + env.put(FOO, "BAR"); + env.put(BIZ, "BAZ"); + when(system2.envVariables()).thenReturn(env); + publisher.init(writer); + + String content = FileUtils.readFileToString(writer.getFileStructure().analysisLog()); + assertThat(content).containsOnlyOnce(FOO); + assertThat(content).containsOnlyOnce(BIZ); + assertThat(content).containsSequence(BIZ, FOO); + + publisher.dumpSettings(ProjectDefinition.create() + .setProperty("sonar.projectKey", "foo") + .setProperty("env." + FOO, "BAR")); + + content = FileUtils.readFileToString(writer.getFileStructure().analysisLog()); + assertThat(content).containsOnlyOnce(FOO); + assertThat(content).containsOnlyOnce(BIZ); + assertThat(content).doesNotContain("env." + FOO); + } + + @Test + public void shouldNotDumpSensitiveProperties() throws Exception { + ScannerReportWriter writer = new ScannerReportWriter(temp.newFolder()); + publisher.init(writer); + + assertThat(writer.getFileStructure().analysisLog()).exists(); + + publisher.dumpSettings(ProjectDefinition.create() + .setProperty("sonar.projectKey", "foo") + .setProperty("sonar.projectKey", "foo") + .setProperty("sonar.password", "azerty") + .setProperty("sonar.cpp.license.secured", "AZERTY")); + + assertThat(FileUtils.readFileToString(writer.getFileStructure().analysisLog())).containsSequence( + "sonar.cpp.license.secured=******", + "sonar.password=******", + "sonar.projectKey=foo"); + } + + // SONAR-7371 + @Test + public void dontDumpParentProps() throws Exception { + logTester.setLevel(LoggerLevel.DEBUG); + ScannerReportWriter writer = new ScannerReportWriter(temp.newFolder()); + publisher.init(writer); + + ProjectDefinition module = ProjectDefinition.create() + .setProperty("sonar.projectKey", "foo") + .setProperty(SONAR_SKIP, "true"); + + ProjectDefinition.create() + .setProperty("sonar.projectKey", "parent") + .setProperty(SONAR_SKIP, "true") + .addSubProject(module); + + publisher.dumpSettings(module); + + String content = FileUtils.readFileToString(writer.getFileStructure().analysisLog()); + assertThat(content).doesNotContain(SONAR_SKIP); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/report/ComponentsPublisherTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/report/ComponentsPublisherTest.java new file mode 100644 index 00000000000..0a4ed8d69cd --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/report/ComponentsPublisherTest.java @@ -0,0 +1,170 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.report; + +import java.io.File; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.fs.internal.DefaultInputDir; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.fs.internal.DefaultInputModule; +import org.sonar.api.resources.Directory; +import org.sonar.api.resources.Java; +import org.sonar.api.resources.Project; +import org.sonar.api.utils.DateUtils; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.scan.ImmutableProjectReactor; +import org.sonar.scanner.protocol.Constants.ComponentLinkType; +import org.sonar.scanner.protocol.output.ScannerReportReader; +import org.sonar.scanner.protocol.output.ScannerReportWriter; +import org.sonar.scanner.protocol.output.FileStructure; +import org.sonar.scanner.protocol.output.ScannerReport.Component; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ComponentsPublisherTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + BatchComponentCache resourceCache = new BatchComponentCache(); + + @Test + public void add_components_to_report() throws Exception { + + ProjectDefinition rootDef = ProjectDefinition.create().setKey("foo"); + rootDef.properties().put(CoreProperties.PROJECT_VERSION_PROPERTY, "1.0"); + Project root = new Project("foo").setName("Root project").setDescription("Root description") + .setAnalysisDate(DateUtils.parseDate(("2012-12-12"))); + root.setId(1).setUuid("PROJECT_UUID"); + resourceCache.add(root, null).setInputComponent(new DefaultInputModule("foo")); + + Project module1 = new Project("module1").setName("Module1").setDescription("Module description"); + module1.setParent(root); + module1.setId(2).setUuid("MODULE_UUID"); + resourceCache.add(module1, root).setInputComponent(new DefaultInputModule("module1")); + rootDef.addSubProject(ProjectDefinition.create().setKey("module1")); + + Directory dir = Directory.create("src"); + dir.setEffectiveKey("module1:src"); + dir.setId(3).setUuid("DIR_UUID"); + resourceCache.add(dir, module1).setInputComponent(new DefaultInputDir("foo", "src")); + + org.sonar.api.resources.File file = org.sonar.api.resources.File.create("src/Foo.java", Java.INSTANCE, false); + file.setEffectiveKey("module1:src/Foo.java"); + file.setId(4).setUuid("FILE_UUID"); + resourceCache.add(file, dir).setInputComponent(new DefaultInputFile("module1", "src/Foo.java").setLines(2)); + + org.sonar.api.resources.File fileWithoutLang = org.sonar.api.resources.File.create("src/make", null, false); + fileWithoutLang.setEffectiveKey("module1:src/make"); + fileWithoutLang.setId(5).setUuid("FILE_WITHOUT_LANG_UUID"); + resourceCache.add(fileWithoutLang, dir).setInputComponent(new DefaultInputFile("module1", "src/make").setLines(10)); + + org.sonar.api.resources.File testFile = org.sonar.api.resources.File.create("test/FooTest.java", Java.INSTANCE, true); + testFile.setEffectiveKey("module1:test/FooTest.java"); + testFile.setId(6).setUuid("TEST_FILE_UUID"); + resourceCache.add(testFile, dir).setInputComponent(new DefaultInputFile("module1", "test/FooTest.java").setLines(4)); + + ImmutableProjectReactor reactor = new ImmutableProjectReactor(rootDef); + + ComponentsPublisher publisher = new ComponentsPublisher(reactor, resourceCache); + + File outputDir = temp.newFolder(); + ScannerReportWriter writer = new ScannerReportWriter(outputDir); + publisher.publish(writer); + + assertThat(writer.hasComponentData(FileStructure.Domain.COMPONENT, 1)).isTrue(); + assertThat(writer.hasComponentData(FileStructure.Domain.COMPONENT, 2)).isTrue(); + assertThat(writer.hasComponentData(FileStructure.Domain.COMPONENT, 3)).isTrue(); + assertThat(writer.hasComponentData(FileStructure.Domain.COMPONENT, 4)).isTrue(); + assertThat(writer.hasComponentData(FileStructure.Domain.COMPONENT, 5)).isTrue(); + assertThat(writer.hasComponentData(FileStructure.Domain.COMPONENT, 6)).isTrue(); + + // no such reference + assertThat(writer.hasComponentData(FileStructure.Domain.COMPONENT, 7)).isFalse(); + + ScannerReportReader reader = new ScannerReportReader(outputDir); + Component rootProtobuf = reader.readComponent(1); + assertThat(rootProtobuf.getKey()).isEqualTo("foo"); + assertThat(rootProtobuf.getDescription()).isEqualTo("Root description"); + assertThat(rootProtobuf.getVersion()).isEqualTo("1.0"); + assertThat(rootProtobuf.getLinkCount()).isEqualTo(0); + + Component module1Protobuf = reader.readComponent(2); + assertThat(module1Protobuf.getKey()).isEqualTo("module1"); + assertThat(module1Protobuf.getDescription()).isEqualTo("Module description"); + assertThat(module1Protobuf.getVersion()).isEqualTo("1.0"); + } + + @Test + public void add_components_with_links_and_branch() throws Exception { + // inputs + ProjectDefinition rootDef = ProjectDefinition.create().setKey("foo"); + rootDef.properties().put(CoreProperties.PROJECT_VERSION_PROPERTY, "1.0"); + Project root = new Project("foo:my_branch").setName("Root project") + .setAnalysisDate(DateUtils.parseDate(("2012-12-12"))); + root.setId(1).setUuid("PROJECT_UUID"); + resourceCache.add(root, null).setInputComponent(new DefaultInputModule("foo")); + rootDef.properties().put(CoreProperties.LINKS_HOME_PAGE, "http://home"); + rootDef.properties().put(CoreProperties.PROJECT_BRANCH_PROPERTY, "my_branch"); + + Project module1 = new Project("module1:my_branch").setName("Module1"); + module1.setParent(root); + module1.setId(2).setUuid("MODULE_UUID"); + resourceCache.add(module1, root).setInputComponent(new DefaultInputModule("module1")); + ProjectDefinition moduleDef = ProjectDefinition.create().setKey("module1"); + moduleDef.properties().put(CoreProperties.LINKS_CI, "http://ci"); + rootDef.addSubProject(moduleDef); + + Directory dir = Directory.create("src"); + dir.setEffectiveKey("module1:my_branch:my_branch:src"); + dir.setId(3).setUuid("DIR_UUID"); + resourceCache.add(dir, module1).setInputComponent(new DefaultInputDir("foo", "src")); + + org.sonar.api.resources.File file = org.sonar.api.resources.File.create("src/Foo.java", Java.INSTANCE, false); + file.setEffectiveKey("module1:my_branch:my_branch:src/Foo.java"); + file.setId(4).setUuid("FILE_UUID"); + resourceCache.add(file, dir).setInputComponent(new DefaultInputFile("module1", "src/Foo.java").setLines(2)); + + ImmutableProjectReactor reactor = new ImmutableProjectReactor(rootDef); + + ComponentsPublisher publisher = new ComponentsPublisher(reactor, resourceCache); + + File outputDir = temp.newFolder(); + ScannerReportWriter writer = new ScannerReportWriter(outputDir); + publisher.publish(writer); + + ScannerReportReader reader = new ScannerReportReader(outputDir); + Component rootProtobuf = reader.readComponent(1); + assertThat(rootProtobuf.getVersion()).isEqualTo("1.0"); + assertThat(rootProtobuf.getLinkCount()).isEqualTo(1); + assertThat(rootProtobuf.getLink(0).getType()).isEqualTo(ComponentLinkType.HOME); + assertThat(rootProtobuf.getLink(0).getHref()).isEqualTo("http://home"); + + Component module1Protobuf = reader.readComponent(2); + assertThat(module1Protobuf.getVersion()).isEqualTo("1.0"); + assertThat(module1Protobuf.getLinkCount()).isEqualTo(1); + assertThat(module1Protobuf.getLink(0).getType()).isEqualTo(ComponentLinkType.CI); + assertThat(module1Protobuf.getLink(0).getHref()).isEqualTo("http://ci"); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/report/CoveragePublisherTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/report/CoveragePublisherTest.java new file mode 100644 index 00000000000..72c31cf4928 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/report/CoveragePublisherTest.java @@ -0,0 +1,116 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.report; + +import java.io.File; +import java.util.Date; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.fs.internal.DefaultInputModule; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.resources.Project; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.scan.measure.MeasureCache; +import org.sonar.core.util.CloseableIterator; +import org.sonar.scanner.protocol.output.ScannerReportReader; +import org.sonar.scanner.protocol.output.ScannerReportWriter; +import org.sonar.scanner.protocol.output.ScannerReport.Coverage; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class CoveragePublisherTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private MeasureCache measureCache; + private CoveragePublisher publisher; + + private org.sonar.api.resources.Resource sampleFile; + + @Before + public void prepare() { + Project p = new Project("foo").setAnalysisDate(new Date(1234567L)); + BatchComponentCache resourceCache = new BatchComponentCache(); + sampleFile = org.sonar.api.resources.File.create("src/Foo.php").setEffectiveKey("foo:src/Foo.php"); + resourceCache.add(p, null).setInputComponent(new DefaultInputModule("foo")); + resourceCache.add(sampleFile, null).setInputComponent(new DefaultInputFile("foo", "src/Foo.php").setLines(5)); + measureCache = mock(MeasureCache.class); + when(measureCache.byMetric(anyString(), anyString())).thenReturn(null); + publisher = new CoveragePublisher(resourceCache, measureCache); + } + + @Test + public void publishCoverage() throws Exception { + + Measure utLineHits = new Measure<>(CoreMetrics.COVERAGE_LINE_HITS_DATA).setData("2=1;3=1;5=0;6=3"); + when(measureCache.byMetric("foo:src/Foo.php", CoreMetrics.COVERAGE_LINE_HITS_DATA_KEY)).thenReturn(utLineHits); + + Measure conditionsByLine = new Measure<>(CoreMetrics.CONDITIONS_BY_LINE).setData("3=4"); + when(measureCache.byMetric("foo:src/Foo.php", CoreMetrics.CONDITIONS_BY_LINE_KEY)).thenReturn(conditionsByLine); + + Measure coveredConditionsByUts = new Measure<>(CoreMetrics.COVERED_CONDITIONS_BY_LINE).setData("3=2"); + when(measureCache.byMetric("foo:src/Foo.php", CoreMetrics.COVERED_CONDITIONS_BY_LINE_KEY)).thenReturn(coveredConditionsByUts); + + Measure itLineHits = new Measure<>(CoreMetrics.IT_COVERAGE_LINE_HITS_DATA).setData("2=0;3=0;5=1"); + when(measureCache.byMetric("foo:src/Foo.php", CoreMetrics.IT_COVERAGE_LINE_HITS_DATA_KEY)).thenReturn(itLineHits); + + Measure coveredConditionsByIts = new Measure<>(CoreMetrics.IT_COVERED_CONDITIONS_BY_LINE).setData("3=1"); + when(measureCache.byMetric("foo:src/Foo.php", CoreMetrics.IT_COVERED_CONDITIONS_BY_LINE_KEY)).thenReturn(coveredConditionsByIts); + + Measure overallCoveredConditions = new Measure<>(CoreMetrics.OVERALL_COVERED_CONDITIONS_BY_LINE).setData("3=2"); + when(measureCache.byMetric("foo:src/Foo.php", CoreMetrics.OVERALL_COVERED_CONDITIONS_BY_LINE_KEY)).thenReturn(overallCoveredConditions); + + File outputDir = temp.newFolder(); + ScannerReportWriter writer = new ScannerReportWriter(outputDir); + + publisher.publish(writer); + + try (CloseableIterator<Coverage> it = new ScannerReportReader(outputDir).readComponentCoverage(2)) { + assertThat(it.next()).isEqualTo(Coverage.newBuilder() + .setLine(2) + .setUtHits(true) + .setItHits(false) + .build()); + assertThat(it.next()).isEqualTo(Coverage.newBuilder() + .setLine(3) + .setUtHits(true) + .setItHits(false) + .setConditions(4) + .setUtCoveredConditions(2) + .setItCoveredConditions(1) + .setOverallCoveredConditions(2) + .build()); + assertThat(it.next()).isEqualTo(Coverage.newBuilder() + .setLine(5) + .setUtHits(false) + .setItHits(true) + .build()); + } + + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/report/MeasuresPublisherTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/report/MeasuresPublisherTest.java new file mode 100644 index 00000000000..0c7a29dd377 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/report/MeasuresPublisherTest.java @@ -0,0 +1,113 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.report; + +import java.io.File; +import java.util.Collections; +import java.util.Date; +import org.apache.commons.lang.exception.ExceptionUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.scan.measure.MeasureCache; +import org.sonar.core.metric.BatchMetrics; +import org.sonar.core.util.CloseableIterator; +import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.scanner.protocol.output.ScannerReportReader; +import org.sonar.scanner.protocol.output.ScannerReportWriter; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class MeasuresPublisherTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private MeasureCache measureCache; + private MeasuresPublisher publisher; + + private org.sonar.api.resources.Resource sampleFile; + + @Before + public void prepare() { + Project p = new Project("foo").setAnalysisDate(new Date(1234567L)); + BatchComponentCache resourceCache = new BatchComponentCache(); + sampleFile = org.sonar.api.resources.File.create("src/Foo.php").setEffectiveKey("foo:src/Foo.php"); + resourceCache.add(p, null); + resourceCache.add(sampleFile, null); + measureCache = mock(MeasureCache.class); + when(measureCache.byResource(any(Resource.class))).thenReturn(Collections.<Measure>emptyList()); + publisher = new MeasuresPublisher(resourceCache, measureCache, new BatchMetrics()); + } + + @Test + public void publishMeasures() throws Exception { + Measure measure = new Measure<>(CoreMetrics.LINES_TO_COVER) + .setValue(2.0); + // String value + Measure stringMeasure = new Measure<>(CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION) + .setData("foo bar"); + when(measureCache.byResource(sampleFile)).thenReturn(asList(measure, stringMeasure)); + + File outputDir = temp.newFolder(); + ScannerReportWriter writer = new ScannerReportWriter(outputDir); + + publisher.publish(writer); + + ScannerReportReader reader = new ScannerReportReader(outputDir); + + assertThat(reader.readComponentMeasures(1)).hasSize(0); + try (CloseableIterator<ScannerReport.Measure> componentMeasures = reader.readComponentMeasures(2)) { + assertThat(componentMeasures).hasSize(2); + } + } + + @Test + public void fail_with_IAE_when_measure_has_no_value() throws Exception { + Measure measure = new Measure<>(CoreMetrics.LINES_TO_COVER); + when(measureCache.byResource(sampleFile)).thenReturn(Collections.singletonList(measure)); + + File outputDir = temp.newFolder(); + ScannerReportWriter writer = new ScannerReportWriter(outputDir); + + try { + publisher.publish(writer); + fail(); + } catch (RuntimeException e) { + assertThat(ExceptionUtils.getFullStackTrace(e)).contains("Measure on metric 'lines_to_cover' and component 'foo:src/Foo.php' has no value, but it's not allowed"); + } + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/report/MetadataPublisherTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/report/MetadataPublisherTest.java new file mode 100644 index 00000000000..7069845e9cf --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/report/MetadataPublisherTest.java @@ -0,0 +1,100 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.report; + +import java.io.File; +import java.util.Date; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.config.Settings; +import org.sonar.api.resources.Project; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.scan.ImmutableProjectReactor; +import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.scanner.protocol.output.ScannerReportReader; +import org.sonar.scanner.protocol.output.ScannerReportWriter; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MetadataPublisherTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private ProjectDefinition projectDef; + private Project project; + private MetadataPublisher underTest; + private Settings settings; + + @Before + public void prepare() { + projectDef = ProjectDefinition.create().setKey("foo"); + project = new Project("foo").setAnalysisDate(new Date(1234567L)); + BatchComponentCache componentCache = new BatchComponentCache(); + org.sonar.api.resources.Resource sampleFile = org.sonar.api.resources.File.create("src/Foo.php").setEffectiveKey("foo:src/Foo.php"); + componentCache.add(project, null); + componentCache.add(sampleFile, project); + settings = new Settings(); + underTest = new MetadataPublisher(componentCache, new ImmutableProjectReactor(projectDef), settings); + } + + @Test + public void write_metadata() throws Exception { + settings.setProperty(CoreProperties.CPD_CROSS_PROJECT, "true"); + File outputDir = temp.newFolder(); + ScannerReportWriter writer = new ScannerReportWriter(outputDir); + + underTest.publish(writer); + + ScannerReportReader reader = new ScannerReportReader(outputDir); + ScannerReport.Metadata metadata = reader.readMetadata(); + assertThat(metadata.getAnalysisDate()).isEqualTo(1234567L); + assertThat(metadata.getProjectKey()).isEqualTo("foo"); + assertThat(metadata.getProjectKey()).isEqualTo("foo"); + assertThat(metadata.getCrossProjectDuplicationActivated()).isTrue(); + } + + @Test + public void write_project_branch() throws Exception { + settings.setProperty(CoreProperties.CPD_CROSS_PROJECT, "true"); + settings.setProperty(CoreProperties.PROJECT_BRANCH_PROPERTY, "myBranch"); + projectDef.properties().put(CoreProperties.PROJECT_BRANCH_PROPERTY, "myBranch"); + project.setKey("foo:myBranch"); + project.setEffectiveKey("foo:myBranch"); + + File outputDir = temp.newFolder(); + ScannerReportWriter writer = new ScannerReportWriter(outputDir); + + underTest.publish(writer); + + ScannerReportReader reader = new ScannerReportReader(outputDir); + ScannerReport.Metadata metadata = reader.readMetadata(); + assertThat(metadata.getAnalysisDate()).isEqualTo(1234567L); + assertThat(metadata.getProjectKey()).isEqualTo("foo"); + assertThat(metadata.getBranch()).isEqualTo("myBranch"); + // Cross project duplication disabled on branches + assertThat(metadata.getCrossProjectDuplicationActivated()).isFalse(); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/report/ReportPublisherTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/report/ReportPublisherTest.java new file mode 100644 index 00000000000..bb54bfb9113 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/report/ReportPublisherTest.java @@ -0,0 +1,164 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.report; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.mockito.Mockito; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.config.PropertyDefinitions; +import org.sonar.api.config.Settings; +import org.sonar.api.utils.MessageException; +import org.sonar.api.utils.TempFolder; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; +import org.sonar.batch.analysis.DefaultAnalysisMode; +import org.sonar.batch.bootstrap.BatchWsClient; +import org.sonar.batch.scan.ImmutableProjectReactor; +import org.sonar.core.config.CorePropertyDefinitions; + +import static org.apache.commons.io.FileUtils.readFileToString; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ReportPublisherTest { + + @Rule + public LogTester logTester = new LogTester(); + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public ExpectedException exception = ExpectedException.none(); + + DefaultAnalysisMode mode = mock(DefaultAnalysisMode.class); + Settings settings = new Settings(new PropertyDefinitions(CorePropertyDefinitions.all())); + BatchWsClient wsClient = mock(BatchWsClient.class, Mockito.RETURNS_DEEP_STUBS); + ImmutableProjectReactor reactor = mock(ImmutableProjectReactor.class); + ProjectDefinition root; + AnalysisContextReportPublisher contextPublisher = mock(AnalysisContextReportPublisher.class); + + @Before + public void setUp() { + root = ProjectDefinition.create().setKey("struts").setWorkDir(temp.getRoot()); + when(reactor.getRoot()).thenReturn(root); + when(wsClient.baseUrl()).thenReturn("https://localhost/"); + } + + @Test + public void log_and_dump_information_about_report_uploading() throws IOException { + ReportPublisher underTest = new ReportPublisher(settings, wsClient, contextPublisher, reactor, mode, mock(TempFolder.class), new ReportPublisherStep[0]); + + underTest.logSuccess("TASK-123"); + + assertThat(logTester.logs(LoggerLevel.INFO)) + .contains("ANALYSIS SUCCESSFUL, you can browse https://localhost/dashboard/index/struts") + .contains("Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report") + .contains("More about the report processing at https://localhost/api/ce/task?id=TASK-123"); + + File detailsFile = new File(temp.getRoot(), "report-task.txt"); + assertThat(readFileToString(detailsFile)).isEqualTo( + "projectKey=struts\n" + + "serverUrl=https://localhost\n" + + "dashboardUrl=https://localhost/dashboard/index/struts\n" + + "ceTaskId=TASK-123\n" + + "ceTaskUrl=https://localhost/api/ce/task?id=TASK-123\n" + ); + } + + @Test + public void log_public_url_if_defined() throws IOException { + settings.setProperty(CoreProperties.SERVER_BASE_URL, "https://publicserver/sonarqube"); + ReportPublisher underTest = new ReportPublisher(settings, wsClient, contextPublisher, reactor, mode, mock(TempFolder.class), new ReportPublisherStep[0]); + + underTest.logSuccess("TASK-123"); + + assertThat(logTester.logs(LoggerLevel.INFO)) + .contains("ANALYSIS SUCCESSFUL, you can browse https://publicserver/sonarqube/dashboard/index/struts") + .contains("More about the report processing at https://publicserver/sonarqube/api/ce/task?id=TASK-123"); + + File detailsFile = new File(temp.getRoot(), "report-task.txt"); + assertThat(readFileToString(detailsFile)).isEqualTo( + "projectKey=struts\n" + + "serverUrl=https://publicserver/sonarqube\n" + + "dashboardUrl=https://publicserver/sonarqube/dashboard/index/struts\n" + + "ceTaskId=TASK-123\n" + + "ceTaskUrl=https://publicserver/sonarqube/api/ce/task?id=TASK-123\n" + ); + } + + @Test + public void fail_if_public_url_malformed() throws IOException { + settings.setProperty(CoreProperties.SERVER_BASE_URL, "invalid"); + ReportPublisher underTest = new ReportPublisher(settings, wsClient, contextPublisher, reactor, mode, mock(TempFolder.class), new ReportPublisherStep[0]); + + exception.expect(MessageException.class); + exception.expectMessage("Failed to parse public URL set in SonarQube server: invalid"); + underTest.start(); + } + + @Test + public void log_but_not_dump_information_when_report_is_not_uploaded() { + ReportPublisher underTest = new ReportPublisher(settings, wsClient, contextPublisher, reactor, mode, mock(TempFolder.class), new ReportPublisherStep[0]); + + underTest.logSuccess(/* report not uploaded, no server task */null); + + assertThat(logTester.logs(LoggerLevel.INFO)) + .contains("ANALYSIS SUCCESSFUL") + .doesNotContain("dashboard/index"); + + File detailsFile = new File(temp.getRoot(), ReportPublisher.METADATA_DUMP_FILENAME); + assertThat(detailsFile).doesNotExist(); + } + + @Test + public void should_not_delete_report_if_property_is_set() throws IOException { + settings.setProperty("sonar.batch.keepReport", true); + Path reportDir = temp.getRoot().toPath().resolve("batch-report"); + Files.createDirectory(reportDir); + ReportPublisher underTest = new ReportPublisher(settings, wsClient, contextPublisher, reactor, mode, mock(TempFolder.class), new ReportPublisherStep[0]); + + underTest.start(); + underTest.stop(); + assertThat(reportDir).isDirectory(); + } + + @Test + public void should_delete_report_by_default() throws IOException { + Path reportDir = temp.getRoot().toPath().resolve("batch-report"); + Files.createDirectory(reportDir); + ReportPublisher job = new ReportPublisher(settings, wsClient, contextPublisher, reactor, mode, mock(TempFolder.class), new ReportPublisherStep[0]); + + job.start(); + job.stop(); + assertThat(reportDir).doesNotExist(); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/report/SourcePublisherTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/report/SourcePublisherTest.java new file mode 100644 index 00000000000..683d6fecb8a --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/report/SourcePublisherTest.java @@ -0,0 +1,119 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.report; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.fs.internal.DefaultInputModule; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Qualifiers; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.scanner.protocol.output.ScannerReportWriter; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SourcePublisherTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private SourcePublisher publisher; + + private File sourceFile; + + private ScannerReportWriter writer; + + private org.sonar.api.resources.File sampleFile; + + @Before + public void prepare() throws IOException { + Project p = new Project("foo").setAnalysisDate(new Date(1234567L)); + BatchComponentCache resourceCache = new BatchComponentCache(); + sampleFile = org.sonar.api.resources.File.create("src/Foo.php"); + sampleFile.setEffectiveKey("foo:src/Foo.php"); + resourceCache.add(p, null).setInputComponent(new DefaultInputModule("foo")); + File baseDir = temp.newFolder(); + sourceFile = new File(baseDir, "src/Foo.php"); + resourceCache.add(sampleFile, null).setInputComponent( + new DefaultInputFile("foo", "src/Foo.php").setLines(5).setModuleBaseDir(baseDir.toPath()).setCharset(StandardCharsets.ISO_8859_1)); + publisher = new SourcePublisher(resourceCache); + File outputDir = temp.newFolder(); + writer = new ScannerReportWriter(outputDir); + } + + @Test + public void publishEmptySource() throws Exception { + FileUtils.write(sourceFile, "", StandardCharsets.ISO_8859_1); + + publisher.publish(writer); + + File out = writer.getSourceFile(2); + assertThat(FileUtils.readFileToString(out, StandardCharsets.UTF_8)).isEqualTo(""); + } + + @Test + public void publishSourceWithLastEmptyLine() throws Exception { + FileUtils.write(sourceFile, "1\n2\n3\n4\n", StandardCharsets.ISO_8859_1); + + publisher.publish(writer); + + File out = writer.getSourceFile(2); + assertThat(FileUtils.readFileToString(out, StandardCharsets.UTF_8)).isEqualTo("1\n2\n3\n4\n"); + } + + @Test + public void publishTestSource() throws Exception { + FileUtils.write(sourceFile, "1\n2\n3\n4\n", StandardCharsets.ISO_8859_1); + sampleFile.setQualifier(Qualifiers.UNIT_TEST_FILE); + + publisher.publish(writer); + + File out = writer.getSourceFile(2); + assertThat(FileUtils.readFileToString(out, StandardCharsets.UTF_8)).isEqualTo("1\n2\n3\n4\n"); + } + + @Test + public void publishSourceWithLastLineNotEmpty() throws Exception { + FileUtils.write(sourceFile, "1\n2\n3\n4\n5", StandardCharsets.ISO_8859_1); + + publisher.publish(writer); + + File out = writer.getSourceFile(2); + assertThat(FileUtils.readFileToString(out, StandardCharsets.UTF_8)).isEqualTo("1\n2\n3\n4\n5"); + } + + @Test + public void cleanLineEnds() throws Exception { + FileUtils.write(sourceFile, "\n2\r\n3\n4\r5", StandardCharsets.ISO_8859_1); + + publisher.publish(writer); + + File out = writer.getSourceFile(2); + assertThat(FileUtils.readFileToString(out, StandardCharsets.UTF_8)).isEqualTo("\n2\n3\n4\n5"); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/repository/DefaultGlobalRepositoriesLoaderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/repository/DefaultGlobalRepositoriesLoaderTest.java new file mode 100644 index 00000000000..cb3781e5103 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/repository/DefaultGlobalRepositoriesLoaderTest.java @@ -0,0 +1,78 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.repository; + +import org.apache.commons.lang.mutable.MutableBoolean; +import org.junit.Before; +import org.junit.Test; +import org.sonar.batch.cache.WSLoader; +import org.sonar.batch.cache.WSLoaderResult; +import org.sonar.scanner.protocol.input.GlobalRepositories; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +public class DefaultGlobalRepositoriesLoaderTest { + private static final String BATCH_GLOBAL_URL = "/batch/global"; + private WSLoader wsLoader; + private WSLoaderResult<String> result; + private DefaultGlobalRepositoriesLoader globalRepositoryLoader; + + @Before + public void setUp() { + wsLoader = mock(WSLoader.class); + result = new WSLoaderResult<>(new GlobalRepositories().toJson(), true); + when(wsLoader.loadString(BATCH_GLOBAL_URL)).thenReturn(result); + + globalRepositoryLoader = new DefaultGlobalRepositoriesLoader(wsLoader); + } + + @Test + public void test() { + MutableBoolean fromCache = new MutableBoolean(); + globalRepositoryLoader.load(fromCache); + + assertThat(fromCache.booleanValue()).isTrue(); + verify(wsLoader).loadString(BATCH_GLOBAL_URL); + verifyNoMoreInteractions(wsLoader); + } + + @Test + public void testFromServer() { + result = new WSLoaderResult<>(new GlobalRepositories().toJson(), false); + when(wsLoader.loadString(BATCH_GLOBAL_URL)).thenReturn(result); + MutableBoolean fromCache = new MutableBoolean(); + globalRepositoryLoader.load(fromCache); + + assertThat(fromCache.booleanValue()).isFalse(); + verify(wsLoader).loadString(BATCH_GLOBAL_URL); + verifyNoMoreInteractions(wsLoader); + } + + public void testWithoutArg() { + globalRepositoryLoader.load(null); + + verify(wsLoader).loadString(BATCH_GLOBAL_URL); + verifyNoMoreInteractions(wsLoader); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoaderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoaderTest.java new file mode 100644 index 00000000000..d633820ca83 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoaderTest.java @@ -0,0 +1,144 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.repository; + +import com.google.common.io.Resources; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import org.apache.commons.lang.mutable.MutableBoolean; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.utils.MessageException; +import org.sonar.batch.cache.WSLoader; +import org.sonar.batch.cache.WSLoaderResult; +import org.sonarqube.ws.WsBatch.WsProjectResponse; +import org.sonarqube.ws.client.HttpException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class DefaultProjectRepositoriesLoaderTest { + private final static String PROJECT_KEY = "foo?"; + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private DefaultProjectRepositoriesLoader loader; + private WSLoader wsLoader; + + @Before + public void prepare() throws IOException { + wsLoader = mock(WSLoader.class); + InputStream is = mockData(); + when(wsLoader.loadStream(anyString())).thenReturn(new WSLoaderResult<>(is, true)); + loader = new DefaultProjectRepositoriesLoader(wsLoader); + } + + @Test + public void continueOnError() { + when(wsLoader.loadStream(anyString())).thenThrow(IllegalStateException.class); + ProjectRepositories proj = loader.load(PROJECT_KEY, false, null); + assertThat(proj.exists()).isEqualTo(false); + } + + @Test + public void parsingError() throws IOException { + InputStream is = mock(InputStream.class); + when(is.read()).thenThrow(IOException.class); + + when(wsLoader.loadStream(anyString())).thenReturn(new WSLoaderResult<>(is, false)); + loader.load(PROJECT_KEY, false, null); + } + + @Test(expected = IllegalStateException.class) + public void failFastHttpError() { + HttpException http = new HttpException("url", 403); + IllegalStateException e = new IllegalStateException("http error", http); + when(wsLoader.loadStream(anyString())).thenThrow(e); + loader.load(PROJECT_KEY, false, null); + } + + @Test + public void failFastHttpErrorMessageException() { + thrown.expect(MessageException.class); + thrown.expectMessage("http error"); + + HttpException http = new HttpException("uri", 403); + MessageException e = MessageException.of("http error", http); + when(wsLoader.loadStream(anyString())).thenThrow(e); + loader.load(PROJECT_KEY, false, null); + } + + @Test + public void passIssuesModeParameter() { + loader.load(PROJECT_KEY, false, null); + verify(wsLoader).loadStream("/batch/project.protobuf?key=foo%3F"); + + loader.load(PROJECT_KEY, true, null); + verify(wsLoader).loadStream("/batch/project.protobuf?key=foo%3F&issues_mode=true"); + } + + @Test + public void deserializeResponse() throws IOException { + MutableBoolean fromCache = new MutableBoolean(); + loader.load(PROJECT_KEY, false, fromCache); + assertThat(fromCache.booleanValue()).isTrue(); + } + + @Test + public void passAndEncodeProjectKeyParameter() { + loader.load(PROJECT_KEY, false, null); + verify(wsLoader).loadStream("/batch/project.protobuf?key=foo%3F"); + } + + private InputStream mockData() throws IOException { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + WsProjectResponse.Builder projectResponseBuilder = WsProjectResponse.newBuilder(); + WsProjectResponse response = projectResponseBuilder.build(); + response.writeTo(os); + + return new ByteArrayInputStream(os.toByteArray()); + } + + @Test + public void readRealResponse() throws IOException { + InputStream is = getTestResource("project.protobuf"); + when(wsLoader.loadStream(anyString())).thenReturn(new WSLoaderResult<>(is, true)); + + ProjectRepositories proj = loader.load("org.sonarsource.github:sonar-github-plugin", true, null); + FileData fd = proj.fileData("org.sonarsource.github:sonar-github-plugin", + "src/test/java/org/sonar/plugins/github/PullRequestIssuePostJobTest.java"); + + assertThat(fd.revision()).isEqualTo("27bf2c54633d05c5df402bbe09471fe43bd9e2e5"); + assertThat(fd.hash()).isEqualTo("edb6b3b9ab92d8dc53ba90ab86cd422e"); + } + + private InputStream getTestResource(String name) throws IOException { + return Resources.asByteSource(this.getClass().getResource(this.getClass().getSimpleName() + "/" + name)) + .openBufferedStream(); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/repository/DefaultQualityProfileLoaderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/repository/DefaultQualityProfileLoaderTest.java new file mode 100644 index 00000000000..d3b4ce6b87d --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/repository/DefaultQualityProfileLoaderTest.java @@ -0,0 +1,116 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.repository; + +import org.sonar.api.utils.MessageException; + +import org.sonarqube.ws.QualityProfiles; +import com.google.common.io.Resources; +import org.sonarqube.ws.QualityProfiles.SearchWsResponse.QualityProfile; +import org.sonar.batch.cache.WSLoaderResult; +import org.sonar.batch.cache.WSLoader; +import org.junit.Rule; +import org.junit.rules.ExpectedException; +import org.junit.Before; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DefaultQualityProfileLoaderTest { + @Rule + public ExpectedException exception = ExpectedException.none(); + + private DefaultQualityProfileLoader qpLoader; + private WSLoader ws; + private InputStream is; + + @Before + public void setUp() throws IOException { + ws = mock(WSLoader.class); + is = mock(InputStream.class); + when(is.read()).thenReturn(-1); + WSLoaderResult<InputStream> result = new WSLoaderResult<>(is, false); + when(ws.loadStream(anyString())).thenReturn(result); + qpLoader = new DefaultQualityProfileLoader(ws); + } + + @Test + public void testEncoding() throws IOException { + WSLoaderResult<InputStream> result = new WSLoaderResult<>(createEncodedQP("qp"), false); + when(ws.loadStream(anyString())).thenReturn(result); + + List<QualityProfile> loaded = qpLoader.load("foo#2", "my-profile#2", null); + verify(ws).loadStream("/api/qualityprofiles/search.protobuf?projectKey=foo%232&profileName=my-profile%232"); + verifyNoMoreInteractions(ws); + assertThat(loaded).hasSize(1); + } + + @Test + public void testNoProfile() throws IOException { + InputStream is = createEncodedQP(); + when(ws.loadStream(anyString())).thenReturn(new WSLoaderResult<>(is, false)); + + exception.expect(MessageException.class); + exception.expectMessage("No quality profiles"); + + qpLoader.load("project", null, null); + verifyNoMoreInteractions(ws); + } + + @Test + public void use_real_response() throws IOException { + InputStream is = getTestResource("quality_profile_search_default"); + when(ws.loadStream(anyString())).thenReturn(new WSLoaderResult<>(is, false)); + + List<QualityProfile> loaded = qpLoader.loadDefault(null, null); + verify(ws).loadStream("/api/qualityprofiles/search.protobuf?defaults=true"); + verifyNoMoreInteractions(ws); + assertThat(loaded).hasSize(1); + } + + private InputStream getTestResource(String name) throws IOException { + return Resources.asByteSource(this.getClass().getResource(this.getClass().getSimpleName() + "/" + name)) + .openBufferedStream(); + } + + private static InputStream createEncodedQP(String... names) throws IOException { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + QualityProfiles.SearchWsResponse.Builder responseBuilder = QualityProfiles.SearchWsResponse.newBuilder(); + + for (String n : names) { + QualityProfile qp = QualityProfile.newBuilder().setKey(n).setName(n).setLanguage("lang").build(); + responseBuilder.addProfiles(qp); + } + + responseBuilder.build().writeTo(os); + return new ByteArrayInputStream(os.toByteArray()); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/repository/DefaultServerIssuesLoaderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/repository/DefaultServerIssuesLoaderTest.java new file mode 100644 index 00000000000..22416368f3f --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/repository/DefaultServerIssuesLoaderTest.java @@ -0,0 +1,82 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.repository; + +import org.sonar.batch.cache.WSLoaderResult; +import org.sonar.scanner.protocol.input.ScannerInput; +import org.sonar.scanner.protocol.input.ScannerInput.ServerIssue; +import org.sonar.batch.cache.WSLoader; +import com.google.common.base.Function; +import org.junit.Before; +import org.junit.Test; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DefaultServerIssuesLoaderTest { + private DefaultServerIssuesLoader loader; + private WSLoader wsLoader; + + @Before + public void prepare() { + wsLoader = mock(WSLoader.class); + loader = new DefaultServerIssuesLoader(wsLoader); + } + + @Test + public void loadFromWs() throws Exception { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + ServerIssue.newBuilder().setKey("ab1").build() + .writeDelimitedTo(bos); + ServerIssue.newBuilder().setKey("ab2").build() + .writeDelimitedTo(bos); + + InputStream is = new ByteArrayInputStream(bos.toByteArray()); + when(wsLoader.loadStream("/batch/issues.protobuf?key=foo")).thenReturn(new WSLoaderResult<>(is, true)); + + final List<ServerIssue> result = new ArrayList<>(); + loader.load("foo", new Function<ScannerInput.ServerIssue, Void>() { + + @Override + public Void apply(ServerIssue input) { + result.add(input); + return null; + } + }); + + assertThat(result).extracting("key").containsExactly("ab1", "ab2"); + } + + @Test(expected = IllegalStateException.class) + public void testError() throws IOException { + InputStream is = mock(InputStream.class); + when(is.read()).thenThrow(IOException.class); + when(wsLoader.loadStream("/batch/issues.protobuf?key=foo")).thenReturn(new WSLoaderResult<>(is, true)); + loader.load("foo", mock(Function.class)); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/repository/ProjectRepositoriesProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/repository/ProjectRepositoriesProviderTest.java new file mode 100644 index 00000000000..781b48a2a59 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/repository/ProjectRepositoriesProviderTest.java @@ -0,0 +1,115 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.repository; + +import java.util.Date; + +import org.sonar.batch.repository.FileData; +import com.google.common.collect.Table; +import com.google.common.collect.HashBasedTable; +import org.apache.commons.lang.mutable.MutableBoolean; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.sonar.api.batch.bootstrap.ProjectKey; +import org.sonar.batch.analysis.DefaultAnalysisMode; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +public class ProjectRepositoriesProviderTest { + private ProjectRepositoriesProvider provider; + private ProjectRepositories project; + + @Mock + private ProjectRepositoriesLoader loader; + @Mock + private ProjectKey projectKey; + @Mock + private DefaultAnalysisMode mode; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + Table<String, String, String> t1 = HashBasedTable.create(); + Table<String, String, FileData> t2 = HashBasedTable.create(); + + project = new ProjectRepositories(t1, t2, new Date()); + provider = new ProjectRepositoriesProvider(); + + when(projectKey.get()).thenReturn("key"); + } + + @Test + public void testNonAssociated() { + when(mode.isNotAssociated()).thenReturn(true); + ProjectRepositories repo = provider.provide(loader, projectKey, mode); + + assertThat(repo.exists()).isEqualTo(false); + verify(mode).isNotAssociated(); + verifyNoMoreInteractions(loader, projectKey, mode); + } + + @Test + public void singleton() { + when(mode.isNotAssociated()).thenReturn(true); + ProjectRepositories repo = provider.provide(loader, projectKey, mode); + + assertThat(repo.exists()).isEqualTo(false); + verify(mode).isNotAssociated(); + verifyNoMoreInteractions(loader, projectKey, mode); + + repo = provider.provide(loader, projectKey, mode); + verifyNoMoreInteractions(loader, projectKey, mode); + } + + @Test + public void testValidation() { + when(mode.isNotAssociated()).thenReturn(false); + when(mode.isIssues()).thenReturn(true); + when(loader.load(eq("key"), eq(true), any(MutableBoolean.class))).thenReturn(project); + + provider.provide(loader, projectKey, mode); + } + + @Test + public void testAssociated() { + when(mode.isNotAssociated()).thenReturn(false); + when(mode.isIssues()).thenReturn(false); + when(loader.load(eq("key"), eq(false), any(MutableBoolean.class))).thenReturn(project); + + ProjectRepositories repo = provider.provide(loader, projectKey, mode); + + assertThat(repo.exists()).isEqualTo(true); + assertThat(repo.lastAnalysisDate()).isNotNull(); + + verify(mode).isNotAssociated(); + verify(mode, times(2)).isIssues(); + verify(projectKey).get(); + verify(loader).load(eq("key"), eq(false), any(MutableBoolean.class)); + verifyNoMoreInteractions(loader, projectKey, mode); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/repository/QualityProfileProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/repository/QualityProfileProviderTest.java new file mode 100644 index 00000000000..0cd1683b9c9 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/repository/QualityProfileProviderTest.java @@ -0,0 +1,165 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.repository; + +import com.google.common.collect.ImmutableMap; +import java.util.ArrayList; +import java.util.List; +import org.apache.commons.lang.mutable.MutableBoolean; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.sonar.api.batch.bootstrap.ProjectKey; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; +import org.sonar.batch.analysis.AnalysisProperties; +import org.sonar.batch.analysis.DefaultAnalysisMode; +import org.sonar.batch.rule.ModuleQProfiles; +import org.sonarqube.ws.QualityProfiles.SearchWsResponse.QualityProfile; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isNull; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +public class QualityProfileProviderTest { + + @Rule + public LogTester logTester = new LogTester(); + + private QualityProfileProvider qualityProfileProvider; + + @Mock + private QualityProfileLoader loader; + @Mock + private DefaultAnalysisMode mode; + @Mock + private AnalysisProperties props; + @Mock + private ProjectKey key; + @Mock + private ProjectRepositories projectRepo; + + private List<QualityProfile> response; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + qualityProfileProvider = new QualityProfileProvider(); + + when(key.get()).thenReturn("project"); + when(projectRepo.exists()).thenReturn(true); + + response = new ArrayList<>(1); + response.add(QualityProfile.newBuilder().setKey("profile").setName("profile").setLanguage("lang").build()); + } + + @Test + public void testProvide() { + when(mode.isNotAssociated()).thenReturn(false); + when(loader.load(eq("project"), isNull(String.class), any(MutableBoolean.class))).thenReturn(response); + ModuleQProfiles qps = qualityProfileProvider.provide(key, loader, projectRepo, props, mode); + assertResponse(qps); + + verify(loader).load(eq("project"), isNull(String.class), any(MutableBoolean.class)); + verifyNoMoreInteractions(loader); + } + + @Test + public void testNonAssociated() { + when(mode.isNotAssociated()).thenReturn(true); + when(loader.loadDefault(anyString(), any(MutableBoolean.class))).thenReturn(response); + ModuleQProfiles qps = qualityProfileProvider.provide(key, loader, projectRepo, props, mode); + assertResponse(qps); + + verify(loader).loadDefault(anyString(), any(MutableBoolean.class)); + verifyNoMoreInteractions(loader); + } + + @Test + public void testProjectDoesntExist() { + when(mode.isNotAssociated()).thenReturn(false); + when(projectRepo.exists()).thenReturn(false); + when(loader.loadDefault(anyString(), any(MutableBoolean.class))).thenReturn(response); + ModuleQProfiles qps = qualityProfileProvider.provide(key, loader, projectRepo, props, mode); + assertResponse(qps); + + verify(loader).loadDefault(anyString(), any(MutableBoolean.class)); + verifyNoMoreInteractions(loader); + } + + @Test + public void testProfileProp() { + when(mode.isNotAssociated()).thenReturn(false); + when(loader.load(eq("project"), eq("custom"), any(MutableBoolean.class))).thenReturn(response); + when(props.property(ModuleQProfiles.SONAR_PROFILE_PROP)).thenReturn("custom"); + when(props.properties()).thenReturn(ImmutableMap.of(ModuleQProfiles.SONAR_PROFILE_PROP, "custom")); + + ModuleQProfiles qps = qualityProfileProvider.provide(key, loader, projectRepo, props, mode); + assertResponse(qps); + + verify(loader).load(eq("project"), eq("custom"), any(MutableBoolean.class)); + verifyNoMoreInteractions(loader); + assertThat(logTester.logs(LoggerLevel.WARN)).contains("Ability to set quality profile from command line using '" + ModuleQProfiles.SONAR_PROFILE_PROP + + "' is deprecated and will be dropped in a future SonarQube version. Please configure quality profile used by your project on SonarQube server."); + } + + @Test + public void testIgnoreSonarProfileIssuesMode() { + when(mode.isNotAssociated()).thenReturn(false); + when(mode.isIssues()).thenReturn(true); + when(loader.load(eq("project"), (String) eq(null), any(MutableBoolean.class))).thenReturn(response); + when(props.property(ModuleQProfiles.SONAR_PROFILE_PROP)).thenReturn("custom"); + + ModuleQProfiles qps = qualityProfileProvider.provide(key, loader, projectRepo, props, mode); + assertResponse(qps); + + verify(loader).load(eq("project"), (String) eq(null), any(MutableBoolean.class)); + verifyNoMoreInteractions(loader); + } + + @Test + public void testProfilePropDefault() { + when(mode.isNotAssociated()).thenReturn(true); + when(loader.loadDefault(eq("custom"), any(MutableBoolean.class))).thenReturn(response); + when(props.property(ModuleQProfiles.SONAR_PROFILE_PROP)).thenReturn("custom"); + when(props.properties()).thenReturn(ImmutableMap.of(ModuleQProfiles.SONAR_PROFILE_PROP, "custom")); + + ModuleQProfiles qps = qualityProfileProvider.provide(key, loader, projectRepo, props, mode); + assertResponse(qps); + + verify(loader).loadDefault(eq("custom"), any(MutableBoolean.class)); + verifyNoMoreInteractions(loader); + assertThat(logTester.logs(LoggerLevel.WARN)).contains("Ability to set quality profile from command line using '" + ModuleQProfiles.SONAR_PROFILE_PROP + + "' is deprecated and will be dropped in a future SonarQube version. Please configure quality profile used by your project on SonarQube server."); + } + + private void assertResponse(ModuleQProfiles qps) { + assertThat(qps.findAll()).hasSize(1); + assertThat(qps.findAll()).extracting("key").containsExactly("profile"); + + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/repository/user/UserRepositoryLoaderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/repository/user/UserRepositoryLoaderTest.java new file mode 100644 index 00000000000..870afd5737e --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/repository/user/UserRepositoryLoaderTest.java @@ -0,0 +1,119 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.repository.user; + +import org.assertj.core.util.Lists; + +import org.sonar.batch.cache.WSLoaderResult; +import org.sonar.scanner.protocol.input.ScannerInput; +import org.sonar.batch.cache.WSLoader; +import org.junit.Before; +import com.google.common.collect.ImmutableList; +import org.apache.commons.lang.mutable.MutableBoolean; +import com.google.common.collect.ImmutableMap; +import org.junit.rules.ExpectedException; +import org.junit.Rule; +import org.mockito.Mockito; +import org.junit.Test; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Map; + +import static org.mockito.Matchers.anyString; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class UserRepositoryLoaderTest { + @Rule + public final ExpectedException exception = ExpectedException.none(); + + private WSLoader wsLoader; + private UserRepositoryLoader userRepo; + + @Before + public void setUp() { + wsLoader = mock(WSLoader.class); + userRepo = new UserRepositoryLoader(wsLoader); + } + + @Test + public void testLoadEmptyList() { + assertThat(userRepo.load(Lists.<String>emptyList())).isEmpty(); + } + + @Test + public void testLoad() throws IOException { + Map<String, String> userMap = ImmutableMap.of("fmallet", "Freddy Mallet", "sbrandhof", "Simon"); + WSLoaderResult<InputStream> res = new WSLoaderResult<>(createUsersMock(userMap), true); + when(wsLoader.loadStream("/batch/users?logins=fmallet,sbrandhof")).thenReturn(res); + + assertThat(userRepo.load(Arrays.asList("fmallet", "sbrandhof"))).extracting("login", "name").containsOnly(tuple("fmallet", "Freddy Mallet"), tuple("sbrandhof", "Simon")); + } + + @Test + public void testFromCache() throws IOException { + WSLoaderResult<InputStream> res = new WSLoaderResult<>(createUsersMock(ImmutableMap.of("fmallet", "Freddy Mallet")), true); + when(wsLoader.loadStream(anyString())).thenReturn(res); + MutableBoolean fromCache = new MutableBoolean(); + userRepo.load("", fromCache); + assertThat(fromCache.booleanValue()).isTrue(); + + fromCache.setValue(false); + userRepo.load(ImmutableList.of("user"), fromCache); + assertThat(fromCache.booleanValue()).isTrue(); + } + + @Test + public void testLoadSingleUser() throws IOException { + WSLoaderResult<InputStream> res = new WSLoaderResult<>(createUsersMock(ImmutableMap.of("fmallet", "Freddy Mallet")), true); + when(wsLoader.loadStream("/batch/users?logins=fmallet")).thenReturn(res); + + assertThat(userRepo.load("fmallet").getName()).isEqualTo("Freddy Mallet"); + } + + private InputStream createUsersMock(Map<String, String> users) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + for (Map.Entry<String, String> user : users.entrySet()) { + ScannerInput.User.Builder builder = ScannerInput.User.newBuilder(); + builder.setLogin(user.getKey()).setName(user.getValue()).build().writeDelimitedTo(out); + } + return new ByteArrayInputStream(out.toByteArray()); + } + + @Test + public void testInputStreamError() throws IOException { + InputStream is = mock(InputStream.class); + Mockito.doThrow(IOException.class).when(is).read(); + WSLoaderResult<InputStream> res = new WSLoaderResult<>(is, true); + + when(wsLoader.loadStream("/batch/users?logins=fmallet,sbrandhof")).thenReturn(res); + + exception.expect(IllegalStateException.class); + exception.expectMessage("Unable to get user details from server"); + + userRepo.load(Arrays.asList("fmallet", "sbrandhof")); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/ActiveRulesProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/ActiveRulesProviderTest.java new file mode 100644 index 00000000000..1e1b1f0fc3c --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/ActiveRulesProviderTest.java @@ -0,0 +1,97 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.rule; + +import com.google.common.collect.ImmutableList; +import java.util.LinkedList; +import java.util.List; +import org.apache.commons.lang.mutable.MutableBoolean; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.rule.RuleKey; +import org.sonarqube.ws.QualityProfiles.SearchWsResponse.QualityProfile; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +public class ActiveRulesProviderTest { + private ActiveRulesProvider provider; + + @Mock + private DefaultActiveRulesLoader loader; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + provider = new ActiveRulesProvider(); + } + + @Test + public void testCombinationOfRules() { + LoadedActiveRule r1 = mockRule("rule1"); + LoadedActiveRule r2 = mockRule("rule2"); + LoadedActiveRule r3 = mockRule("rule3"); + + List<LoadedActiveRule> qp1Rules = ImmutableList.of(r1, r2); + List<LoadedActiveRule> qp2Rules = ImmutableList.of(r2, r3); + List<LoadedActiveRule> qp3Rules = ImmutableList.of(r1, r3); + + when(loader.load(eq("qp1"), any(MutableBoolean.class))).thenReturn(qp1Rules); + when(loader.load(eq("qp2"), any(MutableBoolean.class))).thenReturn(qp2Rules); + when(loader.load(eq("qp3"), any(MutableBoolean.class))).thenReturn(qp3Rules); + + ModuleQProfiles profiles = mockProfiles("qp1", "qp2", "qp3"); + ActiveRules activeRules = provider.provide(loader, profiles); + + assertThat(activeRules.findAll()).hasSize(3); + assertThat(activeRules.findAll()).extracting("ruleKey").containsOnly( + RuleKey.of("rule1", "rule1"), RuleKey.of("rule2", "rule2"), RuleKey.of("rule3", "rule3")); + + verify(loader).load(eq("qp1"), any(MutableBoolean.class)); + verify(loader).load(eq("qp2"), any(MutableBoolean.class)); + verify(loader).load(eq("qp3"), any(MutableBoolean.class)); + verifyNoMoreInteractions(loader); + } + + private static ModuleQProfiles mockProfiles(String... keys) { + List<QualityProfile> profiles = new LinkedList<>(); + + for (String k : keys) { + QualityProfile p = QualityProfile.newBuilder().setKey(k).setLanguage(k).build(); + profiles.add(p); + } + + return new ModuleQProfiles(profiles); + } + + private static LoadedActiveRule mockRule(String name) { + LoadedActiveRule r = new LoadedActiveRule(); + r.setName(name); + r.setRuleKey(RuleKey.of(name, name)); + return r; + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/DefaultActiveRulesLoaderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/DefaultActiveRulesLoaderTest.java new file mode 100644 index 00000000000..434c84dda93 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/DefaultActiveRulesLoaderTest.java @@ -0,0 +1,85 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.rule; + +import org.sonar.api.rule.RuleKey; +import org.sonar.batch.cache.WSLoaderResult; +import org.sonar.batch.cache.WSLoader; +import com.google.common.io.Resources; +import org.junit.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; + +import static org.mockito.Mockito.verify; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import org.junit.Before; + +public class DefaultActiveRulesLoaderTest { + private DefaultActiveRulesLoader loader; + private WSLoader ws; + + @Before + public void setUp() { + ws = mock(WSLoader.class); + loader = new DefaultActiveRulesLoader(ws); + } + + @Test + public void feed_real_response_encode_qp() throws IOException { + InputStream response1 = loadResource("active_rule_search1.protobuf"); + InputStream response2 = loadResource("active_rule_search2.protobuf"); + + String req1 = "/api/rules/search.protobuf?f=repo,name,severity,lang,internalKey,templateKey,params,actives&activation=true&qprofile=c%2B-test_c%2B-values-17445&p=1&ps=500"; + String req2 = "/api/rules/search.protobuf?f=repo,name,severity,lang,internalKey,templateKey,params,actives&activation=true&qprofile=c%2B-test_c%2B-values-17445&p=2&ps=500"; + when(ws.loadStream(req1)).thenReturn(new WSLoaderResult<>(response1, false)); + when(ws.loadStream(req2)).thenReturn(new WSLoaderResult<>(response2, false)); + + Collection<LoadedActiveRule> activeRules = loader.load("c+-test_c+-values-17445", null); + assertThat(activeRules).hasSize(226); + assertActiveRule(activeRules); + + verify(ws).loadStream(req1); + verify(ws).loadStream(req2); + verifyNoMoreInteractions(ws); + } + + private static void assertActiveRule(Collection<LoadedActiveRule> activeRules) { + RuleKey key = RuleKey.of("squid", "S3008"); + for (LoadedActiveRule r : activeRules) { + if (!r.getRuleKey().equals(key)) { + continue; + } + + assertThat(r.getParams().get("format")).isEqualTo("^[a-z][a-zA-Z0-9]*$"); + assertThat(r.getSeverity()).isEqualTo("MINOR"); + } + } + + private InputStream loadResource(String name) throws IOException { + return Resources.asByteSource(this.getClass().getResource("DefaultActiveRulesLoaderTest/" + name)) + .openBufferedStream(); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/DefaultRulesLoaderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/DefaultRulesLoaderTest.java new file mode 100644 index 00000000000..a5a461c4e9f --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/DefaultRulesLoaderTest.java @@ -0,0 +1,79 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.rule; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import org.junit.rules.ExpectedException; +import org.sonar.batch.cache.WSLoaderResult; +import org.sonar.batch.cache.WSLoader; +import org.apache.commons.lang.mutable.MutableBoolean; +import org.sonarqube.ws.Rules.ListResponse.Rule; +import com.google.common.io.ByteSource; +import com.google.common.io.Resources; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import static org.mockito.Matchers.anyString; +import static org.assertj.core.api.Assertions.assertThat; +import org.junit.Test; + +public class DefaultRulesLoaderTest { + @org.junit.Rule + public ExpectedException exception = ExpectedException.none(); + + @Test + public void testParseServerResponse() throws IOException { + WSLoader wsLoader = mock(WSLoader.class); + InputStream is = Resources.asByteSource(this.getClass().getResource("DefaultRulesLoader/response.protobuf")).openBufferedStream(); + when(wsLoader.loadStream(anyString())).thenReturn(new WSLoaderResult<>(is, true)); + DefaultRulesLoader loader = new DefaultRulesLoader(wsLoader); + List<Rule> ruleList = loader.load(null); + assertThat(ruleList).hasSize(318); + } + + @Test + public void testLoadedFromCache() throws IOException { + WSLoader wsLoader = mock(WSLoader.class); + InputStream is = Resources.asByteSource(this.getClass().getResource("DefaultRulesLoader/response.protobuf")).openBufferedStream(); + when(wsLoader.loadStream(anyString())).thenReturn(new WSLoaderResult<>(is, true)); + DefaultRulesLoader loader = new DefaultRulesLoader(wsLoader); + MutableBoolean fromCache = new MutableBoolean(); + loader.load(fromCache); + + assertThat(fromCache.booleanValue()).isTrue(); + } + + @Test + public void testError() throws IOException { + WSLoader wsLoader = mock(WSLoader.class); + InputStream is = ByteSource.wrap(new String("trash").getBytes()).openBufferedStream(); + when(wsLoader.loadStream(anyString())).thenReturn(new WSLoaderResult<>(is, true)); + DefaultRulesLoader loader = new DefaultRulesLoader(wsLoader); + + exception.expect(IllegalStateException.class); + exception.expectMessage("Unable to get rules"); + + loader.load(null); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/QProfileSensorTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/QProfileSensorTest.java new file mode 100644 index 00000000000..4801f246c55 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/QProfileSensorTest.java @@ -0,0 +1,135 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.rule; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.api.batch.SensorContext; +import org.sonar.api.batch.fs.internal.DefaultFileSystem; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.resources.Project; +import org.sonar.api.test.IsMeasure; +import org.sonar.core.util.UtcDateUtils; + +import java.util.Collections; +import java.util.Date; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class QProfileSensorTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + static final Date DATE = UtcDateUtils.parseDateTime("2014-01-15T12:00:00+0000"); + static final QProfile JAVA_PROFILE = new QProfile().setKey("java-two").setName("Java Two").setLanguage("java") + .setRulesUpdatedAt(DATE); + static final QProfile PHP_PROFILE = new QProfile().setKey("php-one").setName("Php One").setLanguage("php") + .setRulesUpdatedAt(DATE); + + ModuleQProfiles moduleQProfiles = mock(ModuleQProfiles.class); + Project project = mock(Project.class); + SensorContext sensorContext = mock(SensorContext.class); + DefaultFileSystem fs; + + @Before + public void prepare() throws Exception { + fs = new DefaultFileSystem(temp.newFolder().toPath()); + } + + @Test + public void to_string() { + QProfileSensor sensor = new QProfileSensor(moduleQProfiles, fs, mock(AnalysisMode.class)); + assertThat(sensor.toString()).isEqualTo("QProfileSensor"); + } + + @Test + public void no_execution_in_issues_mode() { + AnalysisMode analysisMode = mock(AnalysisMode.class); + when(analysisMode.isIssues()).thenReturn(true); + QProfileSensor sensor = new QProfileSensor(moduleQProfiles, fs, analysisMode); + assertThat(sensor.shouldExecuteOnProject(project)).isFalse(); + + } + + @Test + public void no_qprofiles() { + when(moduleQProfiles.findAll()).thenReturn(Collections.<QProfile>emptyList()); + + QProfileSensor sensor = new QProfileSensor(moduleQProfiles, fs, mock(AnalysisMode.class)); + assertThat(sensor.shouldExecuteOnProject(project)).isTrue(); + sensor.analyse(project, sensorContext); + + // measures are not saved + verify(sensorContext).saveMeasure(argThat(new IsMeasure(CoreMetrics.QUALITY_PROFILES, "[]"))); + } + + @Test + public void mark_profiles_as_used() { + when(moduleQProfiles.findByLanguage("java")).thenReturn(JAVA_PROFILE); + when(moduleQProfiles.findByLanguage("php")).thenReturn(PHP_PROFILE); + when(moduleQProfiles.findByLanguage("abap")).thenReturn(null); + fs.addLanguages("java", "php", "abap"); + + QProfileSensor sensor = new QProfileSensor(moduleQProfiles, fs, mock(AnalysisMode.class)); + assertThat(sensor.shouldExecuteOnProject(project)).isTrue(); + sensor.analyse(project, sensorContext); + } + + @Test + public void store_measures_on_single_lang_module() { + when(moduleQProfiles.findByLanguage("java")).thenReturn(JAVA_PROFILE); + when(moduleQProfiles.findByLanguage("php")).thenReturn(PHP_PROFILE); + when(moduleQProfiles.findByLanguage("abap")).thenReturn(null); + fs.addLanguages("java"); + + QProfileSensor sensor = new QProfileSensor(moduleQProfiles, fs, mock(AnalysisMode.class)); + assertThat(sensor.shouldExecuteOnProject(project)).isTrue(); + sensor.analyse(project, sensorContext); + + verify(sensorContext).saveMeasure( + argThat(new IsMeasure(CoreMetrics.QUALITY_PROFILES, + "[{\"key\":\"java-two\",\"language\":\"java\",\"name\":\"Java Two\",\"rulesUpdatedAt\":\"2014-01-15T12:00:00+0000\"}]"))); + } + + @Test + public void store_measures_on_multi_lang_module() { + when(moduleQProfiles.findByLanguage("java")).thenReturn(JAVA_PROFILE); + when(moduleQProfiles.findByLanguage("php")).thenReturn(PHP_PROFILE); + when(moduleQProfiles.findByLanguage("abap")).thenReturn(null); + fs.addLanguages("java", "php"); + + QProfileSensor sensor = new QProfileSensor(moduleQProfiles, fs, mock(AnalysisMode.class)); + assertThat(sensor.shouldExecuteOnProject(project)).isTrue(); + sensor.analyse(project, sensorContext); + + verify(sensorContext).saveMeasure( + argThat(new IsMeasure(CoreMetrics.QUALITY_PROFILES, + "[{\"key\":\"java-two\",\"language\":\"java\",\"name\":\"Java Two\",\"rulesUpdatedAt\":\"2014-01-15T12:00:00+0000\"}," + + "{\"key\":\"php-one\",\"language\":\"php\",\"name\":\"Php One\",\"rulesUpdatedAt\":\"2014-01-15T12:00:00+0000\"}]"))); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/QProfileVerifierTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/QProfileVerifierTest.java new file mode 100644 index 00000000000..9df8cd75491 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/QProfileVerifierTest.java @@ -0,0 +1,101 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.rule; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.slf4j.Logger; +import org.sonar.api.batch.fs.internal.DefaultFileSystem; +import org.sonar.api.config.Settings; +import org.sonar.api.utils.MessageException; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class QProfileVerifierTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private DefaultFileSystem fs; + private ModuleQProfiles profiles; + private Settings settings = new Settings(); + + @Before + public void before() throws Exception { + fs = new DefaultFileSystem(temp.newFolder().toPath()); + profiles = mock(ModuleQProfiles.class); + QProfile javaProfile = new QProfile().setKey("p1").setName("My Java profile").setLanguage("java"); + when(profiles.findByLanguage("java")).thenReturn(javaProfile); + QProfile cobolProfile = new QProfile().setKey("p2").setName("My Cobol profile").setLanguage("cobol"); + when(profiles.findByLanguage("cobol")).thenReturn(cobolProfile); + } + + @Test + public void should_log_all_used_profiles() { + fs.addLanguages("java", "cobol"); + QProfileVerifier profileLogger = new QProfileVerifier(settings, fs, profiles); + Logger logger = mock(Logger.class); + profileLogger.execute(logger); + + verify(logger).info("Quality profile for {}: {}", "java", "My Java profile"); + verify(logger).info("Quality profile for {}: {}", "cobol", "My Cobol profile"); + } + + @Test + public void should_fail_if_default_profile_not_used() { + fs.addLanguages("java", "cobol"); + settings.setProperty("sonar.profile", "Unknown"); + + QProfileVerifier profileLogger = new QProfileVerifier(settings, fs, profiles); + + thrown.expect(MessageException.class); + thrown.expectMessage("sonar.profile was set to 'Unknown' but didn't match any profile for any language. Please check your configuration."); + + profileLogger.execute(); + } + + @Test + public void should_not_fail_if_no_language_on_project() { + settings.setProperty("sonar.profile", "Unknown"); + + QProfileVerifier profileLogger = new QProfileVerifier(settings, fs, profiles); + + profileLogger.execute(); + + } + + @Test + public void should_not_fail_if_default_profile_used_at_least_once() { + fs.addLanguages("java", "cobol"); + settings.setProperty("sonar.profile", "My Java profile"); + + QProfileVerifier profileLogger = new QProfileVerifier(settings, fs, profiles); + + profileLogger.execute(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/RuleFinderCompatibilityTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/RuleFinderCompatibilityTest.java new file mode 100644 index 00000000000..abc5f5dfd75 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/RuleFinderCompatibilityTest.java @@ -0,0 +1,101 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.rule; + +import org.sonar.api.batch.rule.internal.RulesBuilder; + +import org.sonar.api.batch.rule.Rules; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rules.RuleQuery; +import static org.assertj.core.api.Assertions.assertThat; + +public class RuleFinderCompatibilityTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Rules rules; + private RuleFinderCompatibility ruleFinder; + + @Before + public void prepare() { + RulesBuilder builder = new RulesBuilder(); + builder.add(RuleKey.of("repo1", "rule1")); + builder.add(RuleKey.of("repo1", "rule2")).setInternalKey("rule2_internal"); + builder.add(RuleKey.of("repo2", "rule1")); + rules = builder.build(); + + ruleFinder = new RuleFinderCompatibility(rules); + } + + @Test + public void testByInternalKey() { + assertThat(ruleFinder.find(RuleQuery.create().withRepositoryKey("repo1").withConfigKey("rule2_internal")).getKey()).isEqualTo("rule2"); + assertThat(ruleFinder.find(RuleQuery.create().withRepositoryKey("repo1").withConfigKey("rule2_internal2"))).isNull(); + } + + @Test + public void testByKey() { + assertThat(ruleFinder.find(RuleQuery.create().withRepositoryKey("repo1").withKey("rule2")).getKey()).isEqualTo("rule2"); + assertThat(ruleFinder.find(RuleQuery.create().withRepositoryKey("repo1").withKey("rule3"))).isNull(); + assertThat(ruleFinder.findByKey("repo1", "rule2").getKey()).isEqualTo("rule2"); + } + + @Test + public void duplicateResult() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Non unique result for rule query: RuleQuery[repositoryKey=repo1,key=<null>,configKey=<null>]"); + ruleFinder.find(RuleQuery.create().withRepositoryKey("repo1")); + } + + @Test + public void unsupportedById() { + thrown.expect(UnsupportedOperationException.class); + ruleFinder.findById(1); + } + + @Test + public void unsupportedByInternalKeyWithoutRepo() { + thrown.expect(UnsupportedOperationException.class); + ruleFinder.find(RuleQuery.create().withConfigKey("config")); + } + + @Test + public void unsupportedByKeyWithoutRepo() { + thrown.expect(UnsupportedOperationException.class); + ruleFinder.find(RuleQuery.create().withKey("key")); + } + + @Test + public void unsupportedByKeyAndInternalKey() { + thrown.expect(UnsupportedOperationException.class); + ruleFinder.find(RuleQuery.create().withRepositoryKey("repo").withKey("key").withConfigKey("config")); + } + + @Test + public void unsupportedByKeyAndInternalKeyWithoutRepo() { + thrown.expect(UnsupportedOperationException.class); + ruleFinder.find(RuleQuery.create().withKey("key").withConfigKey("config")); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/RulesProfileProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/RulesProfileProviderTest.java new file mode 100644 index 00000000000..208a187a966 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/RulesProfileProviderTest.java @@ -0,0 +1,86 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.rule; + +import java.util.Arrays; +import org.junit.Test; +import org.sonar.api.batch.rule.internal.ActiveRulesBuilder; +import org.sonar.api.config.Settings; +import org.sonar.api.profiles.RulesProfile; +import org.sonar.api.rule.RuleKey; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class RulesProfileProviderTest { + + ModuleQProfiles qProfiles = mock(ModuleQProfiles.class); + Settings settings = new Settings(); + RulesProfileProvider provider = new RulesProfileProvider(); + + @Test + public void merge_profiles() { + QProfile qProfile = new QProfile().setKey("java-sw").setName("Sonar way").setLanguage("java"); + when(qProfiles.findAll()).thenReturn(Arrays.asList(qProfile)); + + RulesProfile profile = provider.provide(qProfiles, new ActiveRulesBuilder().build(), settings); + + // merge of all profiles + assertThat(profile).isNotNull().isInstanceOf(RulesProfileWrapper.class); + assertThat(profile.getLanguage()).isEqualTo(""); + assertThat(profile.getName()).isEqualTo("SonarQube"); + assertThat(profile.getActiveRules()).isEmpty(); + try { + profile.getId(); + fail(); + } catch (IllegalStateException e) { + // id must not be used at all + } + } + + @Test + public void keep_compatibility_with_single_language_projects() { + settings.setProperty("sonar.language", "java"); + + QProfile qProfile = new QProfile().setKey("java-sw").setName("Sonar way").setLanguage("java"); + when(qProfiles.findByLanguage("java")).thenReturn(qProfile); + + RulesProfile profile = provider.provide(qProfiles, new ActiveRulesBuilder().build(), settings); + + // no merge, directly the old hibernate profile + assertThat(profile).isNotNull(); + assertThat(profile.getLanguage()).isEqualTo("java"); + assertThat(profile.getName()).isEqualTo("Sonar way"); + } + + @Test + public void support_rule_templates() { + QProfile qProfile = new QProfile().setKey("java-sw").setName("Sonar way").setLanguage("java"); + when(qProfiles.findAll()).thenReturn(Arrays.asList(qProfile)); + ActiveRulesBuilder activeRulesBuilder = new ActiveRulesBuilder(); + activeRulesBuilder.create(RuleKey.of("java", "S001")).setTemplateRuleKey("T001").setLanguage("java").activate(); + + RulesProfile profile = provider.provide(qProfiles, activeRulesBuilder.build(), settings); + + assertThat(profile.getActiveRule("java", "S001").getRule().getTemplate().getKey()).isEqualTo("T001"); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/RulesProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/RulesProviderTest.java new file mode 100644 index 00000000000..2df29eaa61a --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/RulesProviderTest.java @@ -0,0 +1,66 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.rule; + +import static org.mockito.Matchers.any; + +import org.apache.commons.lang.mutable.MutableBoolean; + +import com.google.common.collect.Lists; +import org.sonar.api.batch.rule.Rules; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.mock; +import org.sonarqube.ws.Rules.ListResponse.Rule; +import org.junit.Test; + +public class RulesProviderTest { + @Test + public void testRuleTranslation() { + RulesLoader loader = mock(RulesLoader.class); + when(loader.load(any(MutableBoolean.class))).thenReturn(Lists.newArrayList(getTestRule())); + + RulesProvider provider = new RulesProvider(); + + Rules rules = provider.provide(loader); + + assertThat(rules.findAll()).hasSize(1); + assertRule(rules.findAll().iterator().next()); + } + + private static void assertRule(org.sonar.api.batch.rule.Rule r) { + Rule testRule = getTestRule(); + + assertThat(r.name()).isEqualTo(testRule.getName()); + assertThat(r.internalKey()).isEqualTo(testRule.getInternalKey()); + assertThat(r.key().rule()).isEqualTo(testRule.getKey()); + assertThat(r.key().repository()).isEqualTo(testRule.getRepository()); + } + + private static Rule getTestRule() { + Rule.Builder ruleBuilder = Rule.newBuilder(); + ruleBuilder.setKey("key1"); + ruleBuilder.setRepository("repo1"); + ruleBuilder.setName("name"); + ruleBuilder.setInternalKey("key1"); + return ruleBuilder.build(); + + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/UsedQProfilesTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/UsedQProfilesTest.java new file mode 100644 index 00000000000..dd46501e68e --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/rule/UsedQProfilesTest.java @@ -0,0 +1,72 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.rule; + +import org.junit.Test; +import org.sonar.core.util.UtcDateUtils; + +import java.util.Arrays; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class UsedQProfilesTest { + + static final String JAVA_JSON = "{\"key\":\"p1\",\"language\":\"java\",\"name\":\"Sonar Way\",\"rulesUpdatedAt\":\"2014-01-15T00:00:00+0000\"}"; + static final String PHP_JSON = "{\"key\":\"p2\",\"language\":\"php\",\"name\":\"Sonar Way\",\"rulesUpdatedAt\":\"2014-02-20T00:00:00+0000\"}"; + + @Test + public void from_and_to_json() { + QProfile java = new QProfile().setKey("p1").setName("Sonar Way").setLanguage("java") + .setRulesUpdatedAt(UtcDateUtils.parseDateTime("2014-01-15T00:00:00+0000")); + QProfile php = new QProfile().setKey("p2").setName("Sonar Way").setLanguage("php") + .setRulesUpdatedAt(UtcDateUtils.parseDateTime("2014-02-20T00:00:00+0000")); + + UsedQProfiles used = new UsedQProfiles().add(java).add(php); + String json = "[" + JAVA_JSON + "," + PHP_JSON + "]"; + assertThat(used.toJson()).isEqualTo(json); + + used = UsedQProfiles.fromJson(json); + assertThat(used.profiles()).hasSize(2); + assertThat(used.profiles().first().getKey()).isEqualTo("p1"); + assertThat(used.profiles().last().getKey()).isEqualTo("p2"); + } + + @Test + public void do_not_duplicate_profiles() { + QProfile java = new QProfile().setKey("p1").setName("Sonar Way").setLanguage("java"); + QProfile php = new QProfile().setKey("p2").setName("Sonar Way").setLanguage("php"); + + UsedQProfiles used = new UsedQProfiles().addAll(Arrays.asList(java, java, php)); + assertThat(used.profiles()).hasSize(2); + } + + @Test + public void group_profiles_by_key() { + QProfile java = new QProfile().setKey("p1").setName("Sonar Way").setLanguage("java"); + QProfile php = new QProfile().setKey("p2").setName("Sonar Way").setLanguage("php"); + + UsedQProfiles used = new UsedQProfiles().addAll(Arrays.asList(java, java, php)); + Map<String, QProfile> map = used.profilesByKey(); + assertThat(map).hasSize(2); + assertThat(map.get("p1")).isSameAs(java); + assertThat(map.get("p2")).isSameAs(php); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/LanguageVerifierTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/LanguageVerifierTest.java new file mode 100644 index 00000000000..22a4f21091f --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/LanguageVerifierTest.java @@ -0,0 +1,99 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.internal.DefaultFileSystem; +import org.sonar.api.config.Settings; +import org.sonar.api.resources.Java; +import org.sonar.api.resources.Languages; +import org.sonar.api.utils.MessageException; +import org.sonar.batch.repository.language.DefaultLanguagesRepository; +import org.sonar.batch.repository.language.LanguagesRepository; + +import static org.assertj.core.api.Assertions.assertThat; + +public class LanguageVerifierTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Settings settings = new Settings(); + private LanguagesRepository languages = new DefaultLanguagesRepository(new Languages(Java.INSTANCE)); + private DefaultFileSystem fs; + + @Before + public void prepare() throws Exception { + fs = new DefaultFileSystem(temp.newFolder().toPath()); + } + + @Test + public void language_is_not_set() { + LanguageVerifier verifier = new LanguageVerifier(settings, languages, fs); + verifier.start(); + + // no failure and no language is forced + assertThat(fs.languages()).isEmpty(); + + verifier.stop(); + } + + @Test + public void language_is_empty() { + settings.setProperty("sonar.language", ""); + LanguageVerifier verifier = new LanguageVerifier(settings, languages, fs); + verifier.start(); + + // no failure and no language is forced + assertThat(fs.languages()).isEmpty(); + + verifier.stop(); + } + + @Test + public void language_is_valid() { + settings.setProperty("sonar.language", "java"); + + LanguageVerifier verifier = new LanguageVerifier(settings, languages, fs); + verifier.start(); + + // no failure and language is hardly registered + assertThat(fs.languages()).contains("java"); + + verifier.stop(); + } + + @Test + public void language_is_not_valid() { + thrown.expect(MessageException.class); + thrown.expectMessage("You must install a plugin that supports the language 'php'"); + + settings.setProperty("sonar.language", "php"); + LanguageVerifier verifier = new LanguageVerifier(settings, languages, fs); + verifier.start(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/ModuleSettingsTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/ModuleSettingsTest.java new file mode 100644 index 00000000000..88fb18f06a4 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/ModuleSettingsTest.java @@ -0,0 +1,162 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan; + +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableTable; +import com.google.common.collect.Table; +import java.util.List; +import java.util.Map; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.config.PropertyDefinitions; +import org.sonar.api.utils.MessageException; +import org.sonar.batch.analysis.DefaultAnalysisMode; +import org.sonar.batch.bootstrap.GlobalSettings; +import org.sonar.batch.report.AnalysisContextReportPublisher; +import org.sonar.batch.repository.FileData; +import org.sonar.batch.repository.ProjectRepositories; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ModuleSettingsTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private DefaultAnalysisMode mode; + + @Before + public void before() { + mode = mock(DefaultAnalysisMode.class); + } + + private ProjectRepositories createSettings(String module, Map<String, String> settingsMap) { + Table<String, String, FileData> fileData = ImmutableTable.of(); + Table<String, String, String> settings = HashBasedTable.create(); + + for (Map.Entry<String, String> e : settingsMap.entrySet()) { + settings.put(module, e.getKey(), e.getValue()); + } + return new ProjectRepositories(settings, fileData, null); + } + + @Test + public void testOrderedProjects() { + ProjectDefinition grandParent = ProjectDefinition.create(); + ProjectDefinition parent = ProjectDefinition.create(); + ProjectDefinition child = ProjectDefinition.create(); + grandParent.addSubProject(parent); + parent.addSubProject(child); + + List<ProjectDefinition> hierarchy = ModuleSettings.getTopDownParentProjects(child); + assertThat(hierarchy.get(0)).isEqualTo(grandParent); + assertThat(hierarchy.get(1)).isEqualTo(parent); + assertThat(hierarchy.get(2)).isEqualTo(child); + } + + @Test + public void test_loading_of_module_settings() { + GlobalSettings globalSettings = mock(GlobalSettings.class); + when(globalSettings.getDefinitions()).thenReturn(new PropertyDefinitions()); + when(globalSettings.getProperties()).thenReturn(ImmutableMap.of( + "overridding", "batch", + "on-batch", "true")); + + ProjectRepositories projRepos = createSettings("struts-core", ImmutableMap.of("on-module", "true", "overridding", "module")); + + ProjectDefinition module = ProjectDefinition.create().setKey("struts-core"); + + ModuleSettings moduleSettings = new ModuleSettings(globalSettings, module, projRepos, mode, mock(AnalysisContextReportPublisher.class)); + + assertThat(moduleSettings.getString("overridding")).isEqualTo("module"); + assertThat(moduleSettings.getString("on-batch")).isEqualTo("true"); + assertThat(moduleSettings.getString("on-module")).isEqualTo("true"); + + } + + // SONAR-6386 + @Test + public void test_loading_of_parent_module_settings_for_new_module() { + GlobalSettings globalSettings = mock(GlobalSettings.class); + when(globalSettings.getDefinitions()).thenReturn(new PropertyDefinitions()); + when(globalSettings.getProperties()).thenReturn(ImmutableMap.of( + "overridding", "batch", + "on-batch", "true")); + + ProjectRepositories projRepos = createSettings("struts", ImmutableMap.of("on-module", "true", "overridding", "module")); + + ProjectDefinition module = ProjectDefinition.create().setKey("struts-core"); + ProjectDefinition.create().setKey("struts").addSubProject(module); + + ModuleSettings moduleSettings = new ModuleSettings(globalSettings, module, projRepos, mode, mock(AnalysisContextReportPublisher.class)); + + assertThat(moduleSettings.getString("overridding")).isEqualTo("module"); + assertThat(moduleSettings.getString("on-batch")).isEqualTo("true"); + assertThat(moduleSettings.getString("on-module")).isEqualTo("true"); + } + + @Test + public void should_not_fail_when_accessing_secured_properties() { + GlobalSettings batchSettings = mock(GlobalSettings.class); + when(batchSettings.getDefinitions()).thenReturn(new PropertyDefinitions()); + when(batchSettings.getProperties()).thenReturn(ImmutableMap.of( + "sonar.foo.secured", "bar")); + + ProjectRepositories projSettingsRepo = createSettings("struts-core", ImmutableMap.of("sonar.foo.license.secured", "bar2")); + + ProjectDefinition module = ProjectDefinition.create().setKey("struts-core"); + + ModuleSettings moduleSettings = new ModuleSettings(batchSettings, module, projSettingsRepo, mode, mock(AnalysisContextReportPublisher.class)); + + assertThat(moduleSettings.getString("sonar.foo.license.secured")).isEqualTo("bar2"); + assertThat(moduleSettings.getString("sonar.foo.secured")).isEqualTo("bar"); + } + + @Test + public void should_fail_when_accessing_secured_properties_in_issues() { + GlobalSettings batchSettings = mock(GlobalSettings.class); + when(batchSettings.getDefinitions()).thenReturn(new PropertyDefinitions()); + when(batchSettings.getProperties()).thenReturn(ImmutableMap.of( + "sonar.foo.secured", "bar")); + + ProjectRepositories projSettingsRepo = createSettings("struts-core", ImmutableMap.of("sonar.foo.license.secured", "bar2")); + + when(mode.isIssues()).thenReturn(true); + + ProjectDefinition module = ProjectDefinition.create().setKey("struts-core"); + + ModuleSettings moduleSettings = new ModuleSettings(batchSettings, module, projSettingsRepo, mode, mock(AnalysisContextReportPublisher.class)); + + assertThat(moduleSettings.getString("sonar.foo.license.secured")).isEqualTo("bar2"); + + thrown.expect(MessageException.class); + thrown + .expectMessage( + "Access to the secured property 'sonar.foo.secured' is not possible in issues mode. The SonarQube plugin which requires this property must be deactivated in issues mode."); + moduleSettings.getString("sonar.foo.secured"); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/ProjectExclusionsTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/ProjectExclusionsTest.java new file mode 100644 index 00000000000..4f9b403a94e --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/ProjectExclusionsTest.java @@ -0,0 +1,122 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan; + +import org.junit.Test; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.bootstrap.ProjectReactor; +import org.sonar.api.config.Settings; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ProjectExclusionsTest { + + ProjectReactor newReactor(String rootKey, String... moduleKeys) { + ProjectDefinition root = ProjectDefinition.create().setKey(rootKey); + for (String moduleKey : moduleKeys) { + ProjectDefinition module = ProjectDefinition.create().setKey(moduleKey); + root.addSubProject(module); + } + return new ProjectReactor(root); + } + + @Test + public void testSkippedModules() { + Settings settings = new Settings(); + settings.setProperty("sonar.skippedModules", "sub1,sub3"); + + ProjectReactor reactor = newReactor("root", "sub1", "sub2"); + + ProjectExclusions exclusions = new ProjectExclusions(settings); + exclusions.apply(reactor); + + assertThat(reactor.getProject("root")).isNotNull(); + assertThat(reactor.getProject("sub1")).isNull(); + assertThat(reactor.getProject("sub2")).isNotNull(); + } + + @Test + public void testNoSkippedModules() { + Settings settings = new Settings(); + ProjectReactor reactor = newReactor("root", "sub1", "sub2"); + ProjectExclusions exclusions = new ProjectExclusions(settings); + exclusions.apply(reactor); + + assertThat(reactor.getProject("root")).isNotNull(); + assertThat(reactor.getProject("sub1")).isNotNull(); + assertThat(reactor.getProject("sub2")).isNotNull(); + } + + @Test + public void testIncludedModules() { + Settings settings = new Settings(); + settings.setProperty("sonar.includedModules", "sub1"); + ProjectReactor reactor = newReactor("root", "sub1", "sub2"); + ProjectExclusions exclusions = new ProjectExclusions(settings); + exclusions.apply(reactor); + + assertThat(reactor.getProject("root")).isNotNull(); + assertThat(reactor.getProject("sub1")).isNotNull(); + assertThat(reactor.getProject("sub2")).isNull(); + } + + @Test + public void shouldBeExcludedIfParentIsExcluded() { + ProjectDefinition sub11 = ProjectDefinition.create().setKey("sub11"); + ProjectDefinition sub1 = ProjectDefinition.create().setKey("sub1").addSubProject(sub11); + ProjectDefinition root = ProjectDefinition.create().setKey("root").addSubProject(sub1); + + Settings settings = new Settings(); + settings.setProperty("sonar.skippedModules", "sub1"); + + ProjectReactor reactor = new ProjectReactor(root); + ProjectExclusions exclusions = new ProjectExclusions(settings); + exclusions.apply(reactor); + + assertThat(reactor.getProject("root")).isNotNull(); + assertThat(reactor.getProject("sub1")).isNull(); + assertThat(reactor.getProject("sub11")).isNull(); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldFailIfExcludingRoot() { + Settings settings = new Settings(); + settings.setProperty("sonar.skippedModules", "sub1,root"); + + ProjectReactor reactor = newReactor("root", "sub1", "sub2"); + ProjectExclusions exclusions = new ProjectExclusions(settings); + exclusions.apply(reactor); + } + + @Test + public void shouldIgnoreMavenGroupId() { + ProjectReactor reactor = newReactor("org.apache.struts:struts", "org.apache.struts:struts-core", "org.apache.struts:struts-taglib"); + + Settings settings = new Settings(); + settings.setProperty("sonar.skippedModules", "struts-taglib"); + + ProjectExclusions exclusions = new ProjectExclusions(settings); + exclusions.apply(reactor); + + assertThat(reactor.getProject("org.apache.struts:struts")).isNotNull(); + assertThat(reactor.getProject("org.apache.struts:struts-core")).isNotNull(); + assertThat(reactor.getProject("org.apache.struts:struts-taglib")).isNull(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/ProjectLockTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/ProjectLockTest.java new file mode 100644 index 00000000000..c03240b7341 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/ProjectLockTest.java @@ -0,0 +1,103 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan; + +import org.junit.rules.ExpectedException; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.bootstrap.ProjectReactor; +import org.sonar.home.cache.DirectoryLock; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.assertj.core.api.Assertions.assertThat; +import org.junit.Test; +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; + +public class ProjectLockTest { + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + @Rule + public ExpectedException exception = ExpectedException.none(); + private ProjectLock lock; + + @Before + public void setUp() { + lock = setUpTest(tempFolder.getRoot()); + } + + private ProjectLock setUpTest(File file) { + ProjectReactor projectReactor = mock(ProjectReactor.class); + ProjectDefinition projectDefinition = mock(ProjectDefinition.class); + when(projectReactor.getRoot()).thenReturn(projectDefinition); + when(projectDefinition.getWorkDir()).thenReturn(file); + + return new ProjectLock(projectReactor); + } + + @Test + public void tryLock() { + Path lockFilePath = tempFolder.getRoot().toPath().resolve(DirectoryLock.LOCK_FILE_NAME); + lock.tryLock(); + assertThat(Files.exists(lockFilePath)).isTrue(); + assertThat(Files.isRegularFile(lockFilePath)).isTrue(); + + lock.stop(); + assertThat(Files.exists(lockFilePath)).isTrue(); + } + + @Test + public void tryLockConcurrently() { + exception.expect(IllegalStateException.class); + exception.expectMessage("Another SonarQube analysis is already in progress for this project"); + lock.tryLock(); + lock.tryLock(); + } + + @Test + /** + * If there is an error starting up the scan, we'll still try to unlock even if the lock + * was never done + */ + public void stopWithoutStarting() { + lock.stop(); + lock.stop(); + } + + @Test + public void tryLockTwice() { + lock.tryLock(); + lock.stop(); + lock.tryLock(); + lock.stop(); + } + + @Test + public void unLockWithNoLock() { + lock.stop(); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/ProjectReactorBuilderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/ProjectReactorBuilderTest.java new file mode 100644 index 00000000000..d1a61853ef2 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/ProjectReactorBuilderTest.java @@ -0,0 +1,683 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan; + +import com.google.common.collect.Maps; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.URL; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.bootstrap.ProjectReactor; +import org.sonar.api.utils.MessageException; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; +import org.sonar.batch.analysis.AnalysisProperties; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ProjectReactorBuilderTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + public LogTester logTester = new LogTester(); + + private AnalysisMode mode; + + @Before + public void setUp() { + mode = mock(AnalysisMode.class); + } + + @Test + public void shouldDefineSimpleProject() { + ProjectDefinition projectDefinition = loadProjectDefinition("simple-project"); + + assertThat(projectDefinition.getKey()).isEqualTo("com.foo.project"); + assertThat(projectDefinition.getName()).isEqualTo("Foo Project"); + assertThat(projectDefinition.getVersion()).isEqualTo("1.0-SNAPSHOT"); + assertThat(projectDefinition.getDescription()).isEqualTo("Description of Foo Project"); + assertThat(projectDefinition.getSourceDirs()).contains("sources"); + } + + @Test + public void shouldFailIfUnexistingSourceDirectory() { + thrown.expect(MessageException.class); + thrown.expectMessage("The folder 'unexisting-source-dir' does not exist for 'com.foo.project' (base directory = " + + getResource(this.getClass(), "simple-project-with-unexisting-source-dir") + ")"); + + loadProjectDefinition("simple-project-with-unexisting-source-dir"); + } + + @Test + public void fail_if_sources_not_set() { + thrown.expect(MessageException.class); + thrown.expectMessage("You must define the following mandatory properties for 'com.foo.project': sonar.sources"); + loadProjectDefinition("simple-project-with-missing-source-dir"); + } + + @Test + public void shouldNotFailIfBlankSourceDirectory() { + loadProjectDefinition("simple-project-with-blank-source-dir"); + } + + @Test + public void modulesDuplicateIds() { + thrown.expect(MessageException.class); + thrown.expectMessage("Two modules have the same id: 'module1'. Each module must have a unique id."); + + loadProjectDefinition("multi-module-duplicate-id"); + } + + @Test + public void modulesRepeatedIds() { + ProjectDefinition rootProject = loadProjectDefinition("multi-module-repeated-id"); + + List<ProjectDefinition> modules = rootProject.getSubProjects(); + assertThat(modules.size()).isEqualTo(1); + // Module 1 + ProjectDefinition module1 = modules.get(0); + assertThat(module1.getKey()).isEqualTo("com.foo.project:module1"); + assertThat(module1.getName()).isEqualTo("Foo Module 1"); + + // Module 1 -> Module 1 + ProjectDefinition module1_module1 = module1.getSubProjects().get(0); + assertThat(module1_module1.getKey()).isEqualTo("com.foo.project:module1:module1"); + assertThat(module1_module1.getName()).isEqualTo("Foo Sub Module 1"); + } + + @Test + public void shouldDefineMultiModuleProjectWithDefinitionsAllInRootProject() throws IOException { + ProjectDefinition rootProject = loadProjectDefinition("multi-module-definitions-all-in-root"); + + // CHECK ROOT + assertThat(rootProject.getKey()).isEqualTo("com.foo.project"); + assertThat(rootProject.getName()).isEqualTo("Foo Project"); + assertThat(rootProject.getVersion()).isEqualTo("1.0-SNAPSHOT"); + assertThat(rootProject.getDescription()).isEqualTo("Description of Foo Project"); + // root project must not contain some properties - even if they are defined in the root properties file + assertThat(rootProject.getSourceDirs().contains("sources")).isFalse(); + assertThat(rootProject.getTestDirs().contains("tests")).isFalse(); + // and module properties must have been cleaned + assertThat(rootProject.properties().get("module1.sonar.projectKey")).isNull(); + assertThat(rootProject.properties().get("module2.sonar.projectKey")).isNull(); + // Check baseDir and workDir + assertThat(rootProject.getBaseDir().getCanonicalFile()) + .isEqualTo(getResource(this.getClass(), "multi-module-definitions-all-in-root")); + assertThat(rootProject.getWorkDir().getCanonicalFile()) + .isEqualTo(new File(getResource(this.getClass(), "multi-module-definitions-all-in-root"), ".sonar")); + + // CHECK MODULES + List<ProjectDefinition> modules = rootProject.getSubProjects(); + assertThat(modules.size()).isEqualTo(2); + + // Module 1 + ProjectDefinition module1 = modules.get(0); + assertThat(module1.getBaseDir().getCanonicalFile()).isEqualTo(getResource(this.getClass(), "multi-module-definitions-all-in-root/module1")); + assertThat(module1.getKey()).isEqualTo("com.foo.project:module1"); + assertThat(module1.getName()).isEqualTo("module1"); + assertThat(module1.getVersion()).isEqualTo("1.0-SNAPSHOT"); + // Description should not be inherited from parent if not set + assertThat(module1.getDescription()).isNull(); + assertThat(module1.getSourceDirs()).contains("sources"); + assertThat(module1.getTestDirs()).contains("tests"); + assertThat(module1.getBinaries()).contains("target/classes"); + // and module properties must have been cleaned + assertThat(module1.properties().get("module1.sonar.projectKey")).isNull(); + assertThat(module1.properties().get("module2.sonar.projectKey")).isNull(); + // Check baseDir and workDir + assertThat(module1.getBaseDir().getCanonicalFile()) + .isEqualTo(getResource(this.getClass(), "multi-module-definitions-all-in-root/module1")); + assertThat(module1.getWorkDir().getCanonicalFile()) + .isEqualTo(new File(getResource(this.getClass(), "multi-module-definitions-all-in-root"), ".sonar/com.foo.project_module1")); + + // Module 2 + ProjectDefinition module2 = modules.get(1); + assertThat(module2.getBaseDir().getCanonicalFile()).isEqualTo(getResource(this.getClass(), "multi-module-definitions-all-in-root/module2")); + assertThat(module2.getKey()).isEqualTo("com.foo.project:com.foo.project.module2"); + assertThat(module2.getName()).isEqualTo("Foo Module 2"); + assertThat(module2.getVersion()).isEqualTo("1.0-SNAPSHOT"); + assertThat(module2.getDescription()).isEqualTo("Description of Module 2"); + assertThat(module2.getSourceDirs()).contains("src"); + assertThat(module2.getTestDirs()).contains("tests"); + assertThat(module2.getBinaries()).contains("target/classes"); + // and module properties must have been cleaned + assertThat(module2.properties().get("module1.sonar.projectKey")).isNull(); + assertThat(module2.properties().get("module2.sonar.projectKey")).isNull(); + // Check baseDir and workDir + assertThat(module2.getBaseDir().getCanonicalFile()) + .isEqualTo(getResource(this.getClass(), "multi-module-definitions-all-in-root/module2")); + assertThat(module2.getWorkDir().getCanonicalFile()) + .isEqualTo(new File(getResource(this.getClass(), "multi-module-definitions-all-in-root"), ".sonar/com.foo.project_com.foo.project.module2")); + } + + // SONAR-4876 + @Test + public void shouldDefineMultiModuleProjectWithModuleKey() { + ProjectDefinition rootProject = loadProjectDefinition("multi-module-definitions-moduleKey"); + + // CHECK ROOT + // module properties must have been cleaned + assertThat(rootProject.properties().get("module1.sonar.moduleKey")).isNull(); + assertThat(rootProject.properties().get("module2.sonar.moduleKey")).isNull(); + + // CHECK MODULES + List<ProjectDefinition> modules = rootProject.getSubProjects(); + assertThat(modules.size()).isEqualTo(2); + + // Module 2 + ProjectDefinition module2 = modules.get(1); + assertThat(module2.getKey()).isEqualTo("com.foo.project.module2"); + } + + // SONARPLUGINS-2421 + @Test + public void shouldDefineMultiLanguageProjectWithDefinitionsAllInRootProject() throws IOException { + ProjectDefinition rootProject = loadProjectDefinition("multi-language-definitions-all-in-root"); + + // CHECK ROOT + assertThat(rootProject.getKey()).isEqualTo("example"); + assertThat(rootProject.getName()).isEqualTo("Example"); + assertThat(rootProject.getVersion()).isEqualTo("1.0"); + + // CHECK MODULES + List<ProjectDefinition> modules = rootProject.getSubProjects(); + assertThat(modules.size()).isEqualTo(2); + + // Module 1 + ProjectDefinition module1 = modules.get(0); + assertThat(module1.getBaseDir().getCanonicalFile()).isEqualTo(getResource(this.getClass(), "multi-language-definitions-all-in-root")); + assertThat(module1.getSourceDirs()).contains("src/main/java"); + // and module properties must have been cleaned + assertThat(module1.getWorkDir().getCanonicalFile()) + .isEqualTo(new File(getResource(this.getClass(), "multi-language-definitions-all-in-root"), ".sonar/example_java-module")); + + // Module 2 + ProjectDefinition module2 = modules.get(1); + assertThat(module2.getBaseDir().getCanonicalFile()).isEqualTo(getResource(this.getClass(), "multi-language-definitions-all-in-root")); + assertThat(module2.getSourceDirs()).contains("src/main/groovy"); + // and module properties must have been cleaned + assertThat(module2.getWorkDir().getCanonicalFile()) + .isEqualTo(new File(getResource(this.getClass(), "multi-language-definitions-all-in-root"), ".sonar/example_groovy-module")); + } + + @Test + public void shouldDefineMultiModuleProjectWithBaseDir() { + ProjectDefinition rootProject = loadProjectDefinition("multi-module-with-basedir"); + List<ProjectDefinition> modules = rootProject.getSubProjects(); + assertThat(modules.size()).isEqualTo(1); + assertThat(modules.get(0).getKey()).isEqualTo("com.foo.project:com.foo.project.module1"); + } + + @Test + public void shouldFailIfUnexistingModuleBaseDir() { + thrown.expect(MessageException.class); + thrown.expectMessage("The base directory of the module 'module1' does not exist: " + + getResource(this.getClass(), "multi-module-with-unexisting-basedir").getAbsolutePath() + File.separator + "module1"); + + loadProjectDefinition("multi-module-with-unexisting-basedir"); + } + + @Test + public void shouldFailIfUnexistingSourceFolderInheritedInMultimodule() { + thrown.expect(MessageException.class); + thrown.expectMessage("The folder 'unexisting-source-dir' does not exist for 'com.foo.project:module1' (base directory = " + + getResource(this.getClass(), "multi-module-with-unexisting-source-dir").getAbsolutePath() + File.separator + "module1)"); + + loadProjectDefinition("multi-module-with-unexisting-source-dir"); + } + + @Test + public void shouldFailIfExplicitUnexistingTestFolder() { + thrown.expect(MessageException.class); + thrown.expectMessage("The folder 'tests' does not exist for 'com.foo.project' (base directory = " + + getResource(this.getClass(), "simple-project-with-unexisting-test-dir").getAbsolutePath()); + + loadProjectDefinition("simple-project-with-unexisting-test-dir"); + } + + @Test + public void shouldFailIfExplicitUnexistingTestFolderOnModule() { + thrown.expect(MessageException.class); + thrown.expectMessage("The folder 'tests' does not exist for 'module1' (base directory = " + + getResource(this.getClass(), "multi-module-with-explicit-unexisting-test-dir").getAbsolutePath() + File.separator + "module1)"); + + loadProjectDefinition("multi-module-with-explicit-unexisting-test-dir"); + } + + @Test + public void multiModuleProperties() { + ProjectDefinition projectDefinition = loadProjectDefinition("big-multi-module-definitions-all-in-root"); + + assertThat(projectDefinition.properties().get("module11.property")).isNull(); + assertThat(projectDefinition.properties().get("sonar.profile")).isEqualTo("Foo"); + ProjectDefinition module1 = null; + ProjectDefinition module2 = null; + for (ProjectDefinition prj : projectDefinition.getSubProjects()) { + if (prj.getKey().equals("com.foo.project:module1")) { + module1 = prj; + } else if (prj.getKey().equals("com.foo.project:module2")) { + module2 = prj; + } + } + assertThat(module1.properties().get("module11.property")).isNull(); + assertThat(module1.properties().get("property")).isNull(); + assertThat(module1.properties().get("sonar.profile")).isEqualTo("Foo"); + assertThat(module2.properties().get("module11.property")).isNull(); + assertThat(module2.properties().get("property")).isNull(); + assertThat(module2.properties().get("sonar.profile")).isEqualTo("Foo"); + + ProjectDefinition module11 = null; + ProjectDefinition module12 = null; + for (ProjectDefinition prj : module1.getSubProjects()) { + if (prj.getKey().equals("com.foo.project:module1:module11")) { + module11 = prj; + } else if (prj.getKey().equals("com.foo.project:module1:module12")) { + module12 = prj; + } + } + assertThat(module11.properties().get("module1.module11.property")).isNull(); + assertThat(module11.properties().get("module11.property")).isNull(); + assertThat(module11.properties().get("property")).isEqualTo("My module11 property"); + assertThat(module11.properties().get("sonar.profile")).isEqualTo("Foo"); + assertThat(module12.properties().get("module11.property")).isNull(); + assertThat(module12.properties().get("property")).isNull(); + assertThat(module12.properties().get("sonar.profile")).isEqualTo("Foo"); + } + + @Test + public void shouldRemoveModulePropertiesFromTaskProperties() { + Map<String, String> props = loadProps("big-multi-module-definitions-all-in-root"); + + AnalysisProperties taskProperties = new AnalysisProperties(props, null); + assertThat(taskProperties.property("module1.module11.property")).isEqualTo("My module11 property"); + + new ProjectReactorBuilder(taskProperties, mode).execute(); + + assertThat(taskProperties.property("module1.module11.property")).isNull(); + } + + @Test + public void shouldFailIfMandatoryPropertiesAreNotPresent() { + Map<String, String> props = new HashMap<>(); + props.put("foo1", "bla"); + props.put("foo4", "bla"); + + thrown.expect(MessageException.class); + thrown.expectMessage("You must define the following mandatory properties for 'Unknown': foo2, foo3"); + + ProjectReactorBuilder.checkMandatoryProperties(props, new String[] {"foo1", "foo2", "foo3"}); + } + + @Test + public void shouldFailIfMandatoryPropertiesAreNotPresentButWithProjectKey() { + Map<String, String> props = new HashMap<>(); + props.put("foo1", "bla"); + props.put("sonar.projectKey", "my-project"); + + thrown.expect(MessageException.class); + thrown.expectMessage("You must define the following mandatory properties for 'my-project': foo2, foo3"); + + ProjectReactorBuilder.checkMandatoryProperties(props, new String[] {"foo1", "foo2", "foo3"}); + } + + @Test + public void shouldNotFailIfMandatoryPropertiesArePresent() { + Map<String, String> props = new HashMap<>(); + props.put("foo1", "bla"); + props.put("foo4", "bla"); + + ProjectReactorBuilder.checkMandatoryProperties(props, new String[] {"foo1"}); + + // No exception should be thrown + } + + @Test + public void shouldGetRelativeFile() { + assertThat(ProjectReactorBuilder.resolvePath(getResource(this.getClass(), "/"), "shouldGetFile/foo.properties")) + .isEqualTo(getResource(this.getClass(), "shouldGetFile/foo.properties")); + } + + @Test + public void shouldGetAbsoluteFile() { + File file = getResource(this.getClass(), "shouldGetFile/foo.properties"); + + assertThat(ProjectReactorBuilder.resolvePath(getResource(this.getClass(), "/"), file.getAbsolutePath())) + .isEqualTo(file); + } + + @Test + public void shouldMergeParentProperties() { + // Use a random value to avoid VM optimization that would create constant String and make s1 and s2 the same object + int i = (int) Math.random() * 10; + String s1 = "value" + i; + String s2 = "value" + i; + Map<String, String> parentProps = new HashMap<>(); + parentProps.put("toBeMergeProps", "fooParent"); + parentProps.put("existingChildProp", "barParent"); + parentProps.put("duplicatedProp", s1); + parentProps.put("sonar.projectDescription", "Desc from Parent"); + + Map<String, String> childProps = new HashMap<>(); + childProps.put("existingChildProp", "barChild"); + childProps.put("otherProp", "tutuChild"); + childProps.put("duplicatedProp", s2); + + ProjectReactorBuilder.mergeParentProperties(childProps, parentProps); + + assertThat(childProps).hasSize(4); + assertThat(childProps.get("toBeMergeProps")).isEqualTo("fooParent"); + assertThat(childProps.get("existingChildProp")).isEqualTo("barChild"); + assertThat(childProps.get("otherProp")).isEqualTo("tutuChild"); + assertThat(childProps.get("sonar.projectDescription")).isNull(); + assertThat(childProps.get("duplicatedProp")).isSameAs(parentProps.get("duplicatedProp")); + } + + @Test + public void shouldInitRootWorkDir() { + ProjectReactorBuilder builder = new ProjectReactorBuilder(new AnalysisProperties(Maps.<String, String>newHashMap(), null), mode); + File baseDir = new File("target/tmp/baseDir"); + + File workDir = builder.initRootProjectWorkDir(baseDir, Maps.<String, String>newHashMap()); + + assertThat(workDir).isEqualTo(new File(baseDir, ".sonar")); + } + + @Test + public void nonAssociatedMode() { + when(mode.isIssues()).thenReturn(true); + ProjectDefinition project = loadProjectDefinition("multi-module-with-basedir-not-associated"); + + assertThat(project.getKey()).isEqualTo("project"); + } + + @Test + public void shouldInitWorkDirWithCustomRelativeFolder() { + Map<String, String> props = Maps.<String, String>newHashMap(); + props.put("sonar.working.directory", ".foo"); + ProjectReactorBuilder builder = new ProjectReactorBuilder(new AnalysisProperties(props, null), mode); + File baseDir = new File("target/tmp/baseDir"); + + File workDir = builder.initRootProjectWorkDir(baseDir, props); + + assertThat(workDir).isEqualTo(new File(baseDir, ".foo")); + } + + @Test + public void shouldInitRootWorkDirWithCustomAbsoluteFolder() { + Map<String, String> props = Maps.<String, String>newHashMap(); + props.put("sonar.working.directory", new File("src").getAbsolutePath()); + ProjectReactorBuilder builder = new ProjectReactorBuilder(new AnalysisProperties(props, null), mode); + File baseDir = new File("target/tmp/baseDir"); + + File workDir = builder.initRootProjectWorkDir(baseDir, props); + + assertThat(workDir).isEqualTo(new File("src").getAbsoluteFile()); + } + + @Test + public void shouldFailIf2ModulesWithSameKey() { + Map<String, String> props = new HashMap<>(); + props.put("sonar.projectKey", "root"); + ProjectDefinition root = ProjectDefinition.create().setProperties(props); + + Map<String, String> props1 = new HashMap<>(); + props1.put("sonar.projectKey", "mod1"); + root.addSubProject(ProjectDefinition.create().setProperties(props1)); + + // Check uniqueness of a new module: OK + Map<String, String> props2 = new HashMap<>(); + props2.put("sonar.projectKey", "mod2"); + ProjectDefinition mod2 = ProjectDefinition.create().setProperties(props2); + ProjectReactorBuilder.checkUniquenessOfChildKey(mod2, root); + + // Now, add it and check again + root.addSubProject(mod2); + + thrown.expect(MessageException.class); + thrown.expectMessage("Project 'root' can't have 2 modules with the following key: mod2"); + + ProjectReactorBuilder.checkUniquenessOfChildKey(mod2, root); + } + + @Test + public void shouldSetModuleKeyIfNotPresent() { + Map<String, String> props = new HashMap<>(); + props.put("sonar.projectVersion", "1.0"); + + // should be set + ProjectReactorBuilder.setModuleKeyAndNameIfNotDefined(props, "foo", "parent"); + assertThat(props.get("sonar.moduleKey")).isEqualTo("parent:foo"); + assertThat(props.get("sonar.projectName")).isEqualTo("foo"); + + // but not this 2nd time + ProjectReactorBuilder.setModuleKeyAndNameIfNotDefined(props, "bar", "parent"); + assertThat(props.get("sonar.moduleKey")).isEqualTo("parent:foo"); + assertThat(props.get("sonar.projectName")).isEqualTo("foo"); + } + + private ProjectDefinition loadProjectDefinition(String projectFolder) { + Map<String, String> props = loadProps(projectFolder); + AnalysisProperties bootstrapProps = new AnalysisProperties(props, null); + ProjectReactor projectReactor = new ProjectReactorBuilder(bootstrapProps, mode).execute(); + return projectReactor.getRoot(); + } + + protected static Properties toProperties(File propertyFile) { + Properties propsFromFile = new Properties(); + try (FileInputStream fileInputStream = new FileInputStream(propertyFile)) { + propsFromFile.load(fileInputStream); + } catch (IOException e) { + throw new IllegalStateException("Impossible to read the property file: " + propertyFile.getAbsolutePath(), e); + } + // Trim properties + for (String propKey : propsFromFile.stringPropertyNames()) { + propsFromFile.setProperty(propKey, StringUtils.trim(propsFromFile.getProperty(propKey))); + } + return propsFromFile; + } + + private Map<String, String> loadProps(String projectFolder) { + Map<String, String> props = Maps.<String, String>newHashMap(); + Properties runnerProps = toProperties(getResource(this.getClass(), projectFolder + "/sonar-project.properties")); + for (final String name : runnerProps.stringPropertyNames()) { + props.put(name, runnerProps.getProperty(name)); + } + props.put("sonar.projectBaseDir", getResource(this.getClass(), projectFolder).getAbsolutePath()); + return props; + } + + public Map<String, String> toMap(Properties props) { + Map<String, String> result = new HashMap<>(); + for (Map.Entry<Object, Object> entry : props.entrySet()) { + result.put(entry.getKey().toString(), entry.getValue().toString()); + } + return result; + } + + @Test + public void shouldGetList() { + Map<String, String> props = new HashMap<>(); + + props.put("prop", " foo ,, bar , \n\ntoto,tutu"); + assertThat(ProjectReactorBuilder.getListFromProperty(props, "prop")).containsOnly("foo", "bar", "toto", "tutu"); + } + + @Test + public void shouldGetEmptyList() { + Map<String, String> props = new HashMap<>(); + + props.put("prop", ""); + assertThat(ProjectReactorBuilder.getListFromProperty(props, "prop")).isEmpty(); + } + + @Test + public void shouldGetListFromFile() throws IOException { + String filePath = "shouldGetList/foo.properties"; + Map<String, String> props = loadPropsFromFile(filePath); + + assertThat(ProjectReactorBuilder.getListFromProperty(props, "prop")).containsOnly("foo", "bar", "toto", "tutu"); + } + + @Test + public void shouldDefineProjectWithBuildDir() { + ProjectDefinition rootProject = loadProjectDefinition("simple-project-with-build-dir"); + File buildDir = rootProject.getBuildDir(); + assertThat(buildDir).isDirectory().exists(); + assertThat(new File(buildDir, "report.txt")).isFile().exists(); + assertThat(buildDir.getName()).isEqualTo("build"); + } + + @Test + public void doNotMixPropertiesWhenModuleKeyIsPrefixOfAnother() throws IOException { + ProjectDefinition rootProject = loadProjectDefinition("multi-module-definitions-same-prefix"); + + // CHECK ROOT + assertThat(rootProject.getKey()).isEqualTo("com.foo.project"); + assertThat(rootProject.getName()).isEqualTo("Foo Project"); + assertThat(rootProject.getVersion()).isEqualTo("1.0-SNAPSHOT"); + assertThat(rootProject.getDescription()).isEqualTo("Description of Foo Project"); + // root project must not contain some properties - even if they are defined in the root properties file + assertThat(rootProject.getSourceDirs().contains("sources")).isFalse(); + assertThat(rootProject.getTestDirs().contains("tests")).isFalse(); + // and module properties must have been cleaned + assertThat(rootProject.properties().get("module1.sonar.projectKey")).isNull(); + assertThat(rootProject.properties().get("module2.sonar.projectKey")).isNull(); + // Check baseDir and workDir + assertThat(rootProject.getBaseDir().getCanonicalFile()) + .isEqualTo(getResource(this.getClass(), "multi-module-definitions-same-prefix")); + assertThat(rootProject.getWorkDir().getCanonicalFile()) + .isEqualTo(new File(getResource(this.getClass(), "multi-module-definitions-same-prefix"), ".sonar")); + + // CHECK MODULES + List<ProjectDefinition> modules = rootProject.getSubProjects(); + assertThat(modules.size()).isEqualTo(2); + + // Module 1 + ProjectDefinition module1 = modules.get(0); + assertThat(module1.getBaseDir().getCanonicalFile()).isEqualTo(getResource(this.getClass(), "multi-module-definitions-same-prefix/module1")); + assertThat(module1.getKey()).isEqualTo("com.foo.project:module1"); + assertThat(module1.getName()).isEqualTo("module1"); + assertThat(module1.getVersion()).isEqualTo("1.0-SNAPSHOT"); + // Description should not be inherited from parent if not set + assertThat(module1.getDescription()).isNull(); + assertThat(module1.getSourceDirs()).contains("sources"); + assertThat(module1.getTestDirs()).contains("tests"); + assertThat(module1.getBinaries()).contains("target/classes"); + // and module properties must have been cleaned + assertThat(module1.properties().get("module1.sonar.projectKey")).isNull(); + assertThat(module1.properties().get("module2.sonar.projectKey")).isNull(); + // Check baseDir and workDir + assertThat(module1.getBaseDir().getCanonicalFile()) + .isEqualTo(getResource(this.getClass(), "multi-module-definitions-same-prefix/module1")); + assertThat(module1.getWorkDir().getCanonicalFile()) + .isEqualTo(new File(getResource(this.getClass(), "multi-module-definitions-same-prefix"), ".sonar/com.foo.project_module1")); + + // Module 1 Feature + ProjectDefinition module1Feature = modules.get(1); + assertThat(module1Feature.getBaseDir().getCanonicalFile()).isEqualTo(getResource(this.getClass(), "multi-module-definitions-same-prefix/module1.feature")); + assertThat(module1Feature.getKey()).isEqualTo("com.foo.project:com.foo.project.module1.feature"); + assertThat(module1Feature.getName()).isEqualTo("Foo Module 1 Feature"); + assertThat(module1Feature.getVersion()).isEqualTo("1.0-SNAPSHOT"); + assertThat(module1Feature.getDescription()).isEqualTo("Description of Module 1 Feature"); + assertThat(module1Feature.getSourceDirs()).contains("src"); + assertThat(module1Feature.getTestDirs()).contains("tests"); + assertThat(module1Feature.getBinaries()).contains("target/classes"); + // and module properties must have been cleaned + assertThat(module1Feature.properties().get("module1.sonar.projectKey")).isNull(); + assertThat(module1Feature.properties().get("module2.sonar.projectKey")).isNull(); + // Check baseDir and workDir + assertThat(module1Feature.getBaseDir().getCanonicalFile()) + .isEqualTo(getResource(this.getClass(), "multi-module-definitions-same-prefix/module1.feature")); + assertThat(module1Feature.getWorkDir().getCanonicalFile()) + .isEqualTo(new File(getResource(this.getClass(), "multi-module-definitions-same-prefix"), ".sonar/com.foo.project_com.foo.project.module1.feature")); + } + + @Test + public void should_log_a_warning_when_a_dropped_property_is_present() { + Map<String, String> props = loadProps("simple-project"); + props.put("sonar.qualitygate", "somevalue"); + AnalysisProperties bootstrapProps = new AnalysisProperties(props, null); + new ProjectReactorBuilder(bootstrapProps, mode).execute(); + + assertThat(logTester.logs(LoggerLevel.WARN)).containsOnly("Property 'sonar.qualitygate' is not supported any more. It will be ignored."); + } + + private Map<String, String> loadPropsFromFile(String filePath) throws IOException { + Properties props = new Properties(); + try (FileInputStream fileInputStream = new FileInputStream(getResource(this.getClass(), filePath))) { + props.load(fileInputStream); + } + Map<String, String> result = new HashMap<>(); + for (Map.Entry<Object, Object> entry : props.entrySet()) { + result.put(entry.getKey().toString(), entry.getValue().toString()); + } + return result; + } + + /** + * Search for a test resource in the classpath. For example getResource("org/sonar/MyClass/foo.txt"); + * + * @param path the starting slash is optional + * @return the resource. Null if resource not found + */ + public static File getResource(String path) { + String resourcePath = path; + if (!resourcePath.startsWith("/")) { + resourcePath = "/" + resourcePath; + } + URL url = ProjectReactorBuilderTest.class.getResource(resourcePath); + if (url != null) { + return FileUtils.toFile(url); + } + return null; + } + + /** + * Search for a resource in the classpath. For example calling the method getResource(getClass(), "myTestName/foo.txt") from + * the class org.sonar.Foo loads the file $basedir/src/test/resources/org/sonar/Foo/myTestName/foo.txt + * + * @return the resource. Null if resource not found + */ + public static File getResource(Class baseClass, String path) { + String resourcePath = StringUtils.replaceChars(baseClass.getCanonicalName(), '.', '/'); + if (!path.startsWith("/")) { + resourcePath += "/"; + } + resourcePath += path; + return getResource(resourcePath); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/ProjectReactorValidatorTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/ProjectReactorValidatorTest.java new file mode 100644 index 00000000000..fd5882467b5 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/ProjectReactorValidatorTest.java @@ -0,0 +1,185 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan; + +import static org.mockito.Mockito.when; + +import org.sonar.api.utils.MessageException; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.bootstrap.ProjectReactor; +import org.sonar.api.config.Settings; +import org.sonar.batch.analysis.DefaultAnalysisMode; +import static org.mockito.Mockito.mock; + +public class ProjectReactorValidatorTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private ProjectReactorValidator validator; + private Settings settings; + private DefaultAnalysisMode mode; + + @Before + public void prepare() { + mode = mock(DefaultAnalysisMode.class); + settings = new Settings(); + validator = new ProjectReactorValidator(settings, mode); + } + + @Test + public void not_fail_with_valid_key() { + validator.validate(createProjectReactor("foo")); + validator.validate(createProjectReactor("123foo")); + validator.validate(createProjectReactor("foo123")); + validator.validate(createProjectReactor("1Z3")); + validator.validate(createProjectReactor("a123")); + validator.validate(createProjectReactor("123a")); + validator.validate(createProjectReactor("1:2")); + validator.validate(createProjectReactor("3-3")); + validator.validate(createProjectReactor("-:")); + } + + @Test + public void allow_slash_issues_mode() { + when(mode.isIssues()).thenReturn(true); + validator.validate(createProjectReactor("project/key")); + + when(mode.isIssues()).thenReturn(false); + thrown.expect(MessageException.class); + thrown.expectMessage("is not a valid project or module key"); + validator.validate(createProjectReactor("project/key")); + } + + @Test + public void not_fail_with_alphanumeric_key() { + ProjectReactor reactor = createProjectReactor("Foobar2"); + validator.validate(reactor); + } + + @Test + public void should_not_fail_with_dot_key() { + ProjectReactor reactor = createProjectReactor("foo.bar"); + validator.validate(reactor); + } + + @Test + public void not_fail_with_dash_key() { + ProjectReactor reactor = createProjectReactor("foo-bar"); + validator.validate(reactor); + } + + @Test + public void not_fail_with_colon_key() { + ProjectReactor reactor = createProjectReactor("foo:bar"); + validator.validate(reactor); + } + + @Test + public void not_fail_with_underscore_key() { + ProjectReactor reactor = createProjectReactor("foo_bar"); + validator.validate(reactor); + } + + @Test + public void fail_with_invalid_key() { + ProjectReactor reactor = createProjectReactor("foo$bar"); + + thrown.expect(MessageException.class); + thrown.expectMessage("\"foo$bar\" is not a valid project or module key"); + validator.validate(reactor); + } + + @Test + public void fail_with_backslash_in_key() { + ProjectReactor reactor = createProjectReactor("foo\\bar"); + + thrown.expect(MessageException.class); + thrown.expectMessage("\"foo\\bar\" is not a valid project or module key"); + validator.validate(reactor); + } + + @Test + public void not_fail_with_valid_branch() { + validator.validate(createProjectReactor("foo", "branch")); + validator.validate(createProjectReactor("foo", "Branch2")); + validator.validate(createProjectReactor("foo", "bra.nch")); + validator.validate(createProjectReactor("foo", "bra-nch")); + validator.validate(createProjectReactor("foo", "1")); + validator.validate(createProjectReactor("foo", "bra_nch")); + } + + @Test + public void fail_with_invalid_branch() { + ProjectReactor reactor = createProjectReactor("foo", "bran#ch"); + thrown.expect(MessageException.class); + thrown.expectMessage("\"bran#ch\" is not a valid branch name"); + validator.validate(reactor); + } + + @Test + public void fail_with_colon_in_branch() { + ProjectReactor reactor = createProjectReactor("foo", "bran:ch"); + thrown.expect(MessageException.class); + thrown.expectMessage("\"bran:ch\" is not a valid branch name"); + validator.validate(reactor); + } + + @Test + public void fail_with_only_digits() { + ProjectReactor reactor = createProjectReactor("12345"); + + thrown.expect(MessageException.class); + thrown.expectMessage("\"12345\" is not a valid project or module key"); + validator.validate(reactor); + } + + @Test + public void fail_with_deprecated_sonar_phase() { + ProjectReactor reactor = createProjectReactor("foo"); + settings.setProperty("sonar.phase", "phase"); + + thrown.expect(MessageException.class); + thrown.expectMessage("\"sonar.phase\" is deprecated"); + validator.validate(reactor); + } + + private ProjectReactor createProjectReactor(String projectKey) { + ProjectDefinition def = ProjectDefinition.create().setProperty(CoreProperties.PROJECT_KEY_PROPERTY, projectKey); + ProjectReactor reactor = new ProjectReactor(def); + return reactor; + } + + private ProjectReactor createProjectReactor(String projectKey, String branch) { + ProjectDefinition def = ProjectDefinition.create() + .setProperty(CoreProperties.PROJECT_KEY_PROPERTY, projectKey) + .setProperty(CoreProperties.PROJECT_BRANCH_PROPERTY, branch); + ProjectReactor reactor = new ProjectReactor(def); + settings.setProperty(CoreProperties.PROJECT_BRANCH_PROPERTY, branch); + return reactor; + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/ProjectScanContainerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/ProjectScanContainerTest.java new file mode 100644 index 00000000000..512462923a1 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/ProjectScanContainerTest.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan; + +import org.junit.Test; +import org.sonar.api.BatchExtension; +import org.sonar.api.ServerExtension; +import org.sonar.api.batch.InstantiationStrategy; +import org.sonar.api.task.TaskExtension; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ProjectScanContainerTest { + + @Test + public void should_add_only_batch_extensions() { + ProjectScanContainer.BatchExtensionFilter filter = new ProjectScanContainer.BatchExtensionFilter(); + + assertThat(filter.accept(new MyBatchExtension())).isTrue(); + assertThat(filter.accept(MyBatchExtension.class)).isTrue(); + + assertThat(filter.accept(new MyProjectExtension())).isFalse(); + assertThat(filter.accept(MyProjectExtension.class)).isFalse(); + assertThat(filter.accept(new MyServerExtension())).isFalse(); + assertThat(filter.accept(MyServerExtension.class)).isFalse(); + assertThat(filter.accept(new MyTaskExtension())).isFalse(); + assertThat(filter.accept(MyTaskExtension.class)).isFalse(); + } + + @InstantiationStrategy(InstantiationStrategy.PER_BATCH) + static class MyBatchExtension implements BatchExtension { + + } + + static class MyProjectExtension implements BatchExtension { + + } + + static class MyServerExtension implements ServerExtension { + + } + + static class MyTaskExtension implements TaskExtension { + + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/ProjectSettingsTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/ProjectSettingsTest.java new file mode 100644 index 00000000000..7f9c6eecc4c --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/ProjectSettingsTest.java @@ -0,0 +1,142 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan; + +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.ImmutableTable; +import com.google.common.collect.Table; +import java.util.Collections; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.bootstrap.ProjectReactor; +import org.sonar.api.config.PropertyDefinitions; +import org.sonar.api.utils.MessageException; +import org.sonar.api.utils.log.LogTester; +import org.sonar.batch.analysis.DefaultAnalysisMode; +import org.sonar.batch.bootstrap.GlobalMode; +import org.sonar.batch.bootstrap.GlobalProperties; +import org.sonar.batch.bootstrap.GlobalSettings; +import org.sonar.batch.repository.FileData; +import org.sonar.batch.repository.ProjectRepositories; +import org.sonar.scanner.protocol.input.GlobalRepositories; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ProjectSettingsTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + @Rule + public LogTester logTester = new LogTester(); + + private ProjectRepositories projectRef; + private ProjectDefinition project; + private GlobalSettings bootstrapProps; + private Table<String, String, FileData> emptyFileData; + private Table<String, String, String> emptySettings; + + private GlobalMode globalMode; + private DefaultAnalysisMode mode; + + @Before + public void prepare() { + emptyFileData = ImmutableTable.of(); + emptySettings = ImmutableTable.of(); + project = ProjectDefinition.create().setKey("struts"); + globalMode = mock(GlobalMode.class); + mode = mock(DefaultAnalysisMode.class); + bootstrapProps = new GlobalSettings(new GlobalProperties(Collections.<String, String>emptyMap()), new PropertyDefinitions(), new GlobalRepositories(), globalMode); + } + + @Test + public void should_load_project_props() { + project.setProperty("project.prop", "project"); + + projectRef = new ProjectRepositories(emptySettings, emptyFileData, null); + ProjectSettings batchSettings = new ProjectSettings(new ProjectReactor(project), bootstrapProps, new PropertyDefinitions(), projectRef, mode); + + assertThat(batchSettings.getString("project.prop")).isEqualTo("project"); + } + + @Test + public void should_load_project_root_settings() { + Table<String, String, String> settings = HashBasedTable.create(); + settings.put("struts", "sonar.cpd.cross", "true"); + settings.put("struts", "sonar.java.coveragePlugin", "jacoco"); + + projectRef = new ProjectRepositories(settings, emptyFileData, null); + ProjectSettings batchSettings = new ProjectSettings(new ProjectReactor(project), bootstrapProps, new PropertyDefinitions(), projectRef, mode); + assertThat(batchSettings.getString("sonar.java.coveragePlugin")).isEqualTo("jacoco"); + } + + @Test + public void should_load_project_root_settings_on_branch() { + project.setProperty(CoreProperties.PROJECT_BRANCH_PROPERTY, "mybranch"); + + Table<String, String, String> settings = HashBasedTable.create(); + settings.put("struts:mybranch", "sonar.cpd.cross", "true"); + settings.put("struts:mybranch", "sonar.java.coveragePlugin", "jacoco"); + + projectRef = new ProjectRepositories(settings, emptyFileData, null); + + ProjectSettings batchSettings = new ProjectSettings(new ProjectReactor(project), bootstrapProps, new PropertyDefinitions(), projectRef, mode); + + assertThat(batchSettings.getString("sonar.java.coveragePlugin")).isEqualTo("jacoco"); + } + + @Test + public void should_not_fail_when_accessing_secured_properties() { + Table<String, String, String> settings = HashBasedTable.create(); + settings.put("struts", "sonar.foo.secured", "bar"); + settings.put("struts", "sonar.foo.license.secured", "bar2"); + + projectRef = new ProjectRepositories(settings, emptyFileData, null); + ProjectSettings batchSettings = new ProjectSettings(new ProjectReactor(project), bootstrapProps, new PropertyDefinitions(), projectRef, mode); + + assertThat(batchSettings.getString("sonar.foo.license.secured")).isEqualTo("bar2"); + assertThat(batchSettings.getString("sonar.foo.secured")).isEqualTo("bar"); + } + + @Test + public void should_fail_when_accessing_secured_properties_in_issues_mode() { + Table<String, String, String> settings = HashBasedTable.create(); + settings.put("struts", "sonar.foo.secured", "bar"); + settings.put("struts", "sonar.foo.license.secured", "bar2"); + + when(mode.isIssues()).thenReturn(true); + + projectRef = new ProjectRepositories(settings, emptyFileData, null); + ProjectSettings batchSettings = new ProjectSettings(new ProjectReactor(project), bootstrapProps, new PropertyDefinitions(), projectRef, mode); + + assertThat(batchSettings.getString("sonar.foo.license.secured")).isEqualTo("bar2"); + thrown.expect(MessageException.class); + thrown + .expectMessage( + "Access to the secured property 'sonar.foo.secured' is not possible in issues mode. The SonarQube plugin which requires this property must be deactivated in issues mode."); + batchSettings.getString("sonar.foo.secured"); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/WorkDirectoryCleanerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/WorkDirectoryCleanerTest.java new file mode 100644 index 00000000000..8bd3d5c4e3a --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/WorkDirectoryCleanerTest.java @@ -0,0 +1,79 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.bootstrap.ProjectReactor; +import org.sonar.home.cache.DirectoryLock; + +import java.io.File; +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class WorkDirectoryCleanerTest { + private WorkDirectoryCleaner cleaner; + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Before + public void setUp() throws IOException { + // create files to clean + temp.newFile(); + File newFolder = temp.newFolder(); + File fileInFolder = new File(newFolder, "test"); + fileInFolder.createNewFile(); + + File lock = new File(temp.getRoot(), DirectoryLock.LOCK_FILE_NAME); + lock.createNewFile(); + + // mock project + ProjectReactor projectReactor = mock(ProjectReactor.class); + ProjectDefinition projectDefinition = mock(ProjectDefinition.class); + when(projectReactor.getRoot()).thenReturn(projectDefinition); + when(projectDefinition.getWorkDir()).thenReturn(temp.getRoot()); + + assertThat(temp.getRoot().list().length).isGreaterThan(1); + cleaner = new WorkDirectoryCleaner(projectReactor); + } + + @Test + public void testNonExisting() { + temp.delete(); + cleaner.execute(); + } + + @Test + public void testClean() { + File lock = new File(temp.getRoot(), DirectoryLock.LOCK_FILE_NAME); + cleaner.execute(); + + assertThat(temp.getRoot()).exists(); + assertThat(lock).exists(); + assertThat(temp.getRoot().list()).containsOnly(DirectoryLock.LOCK_FILE_NAME); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/AdditionalFilePredicatesTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/AdditionalFilePredicatesTest.java new file mode 100644 index 00000000000..c0f9fd3c062 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/AdditionalFilePredicatesTest.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.FilePredicate; +import org.sonar.api.batch.fs.internal.DefaultInputFile; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AdditionalFilePredicatesTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void key() { + FilePredicate predicate = new AdditionalFilePredicates.KeyPredicate("struts:Action.java"); + + DefaultInputFile inputFile = new DefaultInputFile("struts", "Action.java"); + assertThat(predicate.apply(inputFile)).isTrue(); + + inputFile = new DefaultInputFile("struts", "Filter.java"); + assertThat(predicate.apply(inputFile)).isFalse(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/ComponentIndexerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/ComponentIndexerTest.java new file mode 100644 index 00000000000..554f175695f --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/ComponentIndexerTest.java @@ -0,0 +1,141 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.sonar.api.batch.fs.InputFile.Status; + +import org.sonar.batch.analysis.DefaultAnalysisMode; + +import java.io.File; +import java.io.IOException; + +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.mockito.ArgumentMatcher; +import org.sonar.api.batch.SonarIndex; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultFileSystem; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.resources.AbstractLanguage; +import org.sonar.api.resources.Directory; +import org.sonar.api.resources.Java; +import org.sonar.api.resources.Languages; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.resources.Resource; +import org.sonar.batch.index.BatchComponent; +import org.sonar.batch.index.BatchComponentCache; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class ComponentIndexerTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + private File baseDir; + private DefaultFileSystem fs; + private SonarIndex sonarIndex; + private AbstractLanguage cobolLanguage; + private Project project; + private ModuleFileSystemInitializer initializer; + private DefaultAnalysisMode mode; + + @Before + public void prepare() throws IOException { + baseDir = temp.newFolder(); + fs = new DefaultFileSystem(baseDir.toPath()); + sonarIndex = mock(SonarIndex.class); + project = new Project("myProject"); + initializer = mock(ModuleFileSystemInitializer.class); + mode = mock(DefaultAnalysisMode.class); + when(initializer.baseDir()).thenReturn(baseDir); + when(initializer.workingDir()).thenReturn(temp.newFolder()); + cobolLanguage = new AbstractLanguage("cobol") { + @Override + public String[] getFileSuffixes() { + return new String[] {"cbl"}; + } + }; + } + + @Test + public void should_index_java_files() throws IOException { + Languages languages = new Languages(Java.INSTANCE); + ComponentIndexer indexer = createIndexer(languages); + DefaultModuleFileSystem fs = new DefaultModuleFileSystem(project, null, mock(FileIndexer.class), initializer, indexer, mode); + fs.add(newInputFile("src/main/java/foo/bar/Foo.java", "", "foo/bar/Foo.java", "java", false, Status.ADDED)); + fs.add(newInputFile("src/main/java2/foo/bar/Foo.java", "", "foo/bar/Foo.java", "java", false, Status.ADDED)); + // should index even if filter is applied + fs.add(newInputFile("src/test/java/foo/bar/FooTest.java", "", "foo/bar/FooTest.java", "java", true, Status.SAME)); + + fs.index(); + + verify(sonarIndex).index(org.sonar.api.resources.File.create("src/main/java/foo/bar/Foo.java", Java.INSTANCE, false)); + verify(sonarIndex).index(org.sonar.api.resources.File.create("src/main/java2/foo/bar/Foo.java", Java.INSTANCE, false)); + verify(sonarIndex).index(argThat(new ArgumentMatcher<org.sonar.api.resources.File>() { + @Override + public boolean matches(Object arg0) { + org.sonar.api.resources.File javaFile = (org.sonar.api.resources.File) arg0; + return javaFile.getKey().equals("src/test/java/foo/bar/FooTest.java") + && javaFile.getPath().equals("src/test/java/foo/bar/FooTest.java") + && javaFile.getQualifier().equals(Qualifiers.UNIT_TEST_FILE); + } + })); + } + + private ComponentIndexer createIndexer(Languages languages) { + BatchComponentCache resourceCache = mock(BatchComponentCache.class); + when(resourceCache.get(any(Resource.class))) + .thenReturn(new BatchComponent(2, org.sonar.api.resources.File.create("foo.php"), new BatchComponent(1, Directory.create("src"), null))); + return new ComponentIndexer(project, languages, sonarIndex, resourceCache); + } + + @Test + public void should_index_cobol_files() throws IOException { + Languages languages = new Languages(cobolLanguage); + ComponentIndexer indexer = createIndexer(languages); + DefaultModuleFileSystem fs = new DefaultModuleFileSystem(project, null, mock(FileIndexer.class), initializer, indexer, mode); + fs.add(newInputFile("src/foo/bar/Foo.cbl", "", "foo/bar/Foo.cbl", "cobol", false, Status.ADDED)); + fs.add(newInputFile("src2/foo/bar/Foo.cbl", "", "foo/bar/Foo.cbl", "cobol", false, Status.ADDED)); + fs.add(newInputFile("src/test/foo/bar/FooTest.cbl", "", "foo/bar/FooTest.cbl", "cobol", true, Status.ADDED)); + + fs.index(); + + verify(sonarIndex).index(org.sonar.api.resources.File.create("/src/foo/bar/Foo.cbl", cobolLanguage, false)); + verify(sonarIndex).index(org.sonar.api.resources.File.create("/src2/foo/bar/Foo.cbl", cobolLanguage, false)); + verify(sonarIndex).index(org.sonar.api.resources.File.create("/src/test/foo/bar/FooTest.cbl", cobolLanguage, true)); + } + + private DefaultInputFile newInputFile(String path, String content, String sourceRelativePath, String languageKey, boolean unitTest, InputFile.Status status) throws IOException { + File file = new File(baseDir, path); + FileUtils.write(file, content); + return new DefaultInputFile("foo", path) + .setLanguage(languageKey) + .setType(unitTest ? InputFile.Type.TEST : InputFile.Type.MAIN) + .setStatus(status); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystemTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystemTest.java new file mode 100644 index 00000000000..46b70a17522 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystemTest.java @@ -0,0 +1,200 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.sonar.api.batch.fs.InputFile.Status; + +import org.junit.Before; +import org.sonar.batch.analysis.DefaultAnalysisMode; +import com.google.common.collect.Lists; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.mockito.Mockito; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.config.Settings; +import org.sonar.api.resources.Project; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +public class DefaultModuleFileSystemTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Settings settings; + private FileIndexer fileIndexer; + private ModuleFileSystemInitializer initializer; + private ComponentIndexer componentIndexer; + private ModuleInputFileCache moduleInputFileCache; + private DefaultAnalysisMode mode; + + @Before + public void setUp() { + settings = new Settings(); + fileIndexer = mock(FileIndexer.class); + initializer = mock(ModuleFileSystemInitializer.class, Mockito.RETURNS_DEEP_STUBS); + componentIndexer = mock(ComponentIndexer.class); + moduleInputFileCache = mock(ModuleInputFileCache.class); + mode = mock(DefaultAnalysisMode.class); + } + + @Test + public void test_equals_and_hashCode() throws Exception { + DefaultModuleFileSystem foo1 = new DefaultModuleFileSystem(moduleInputFileCache, + new Project("foo"), settings, fileIndexer, initializer, componentIndexer, mode); + DefaultModuleFileSystem foo2 = new DefaultModuleFileSystem(moduleInputFileCache, + new Project("foo"), settings, fileIndexer, initializer, componentIndexer, mode); + DefaultModuleFileSystem bar = new DefaultModuleFileSystem(moduleInputFileCache, + new Project("bar"), settings, fileIndexer, initializer, componentIndexer, mode); + DefaultModuleFileSystem branch = new DefaultModuleFileSystem(moduleInputFileCache, + new Project("bar", "branch", "My project"), settings, fileIndexer, initializer, componentIndexer, mode); + + assertThat(foo1.moduleKey()).isEqualTo("foo"); + assertThat(branch.moduleKey()).isEqualTo("bar:branch"); + assertThat(foo1.equals(foo1)).isTrue(); + assertThat(foo1.equals(foo2)).isTrue(); + assertThat(foo1.equals(bar)).isFalse(); + assertThat(foo1.equals("foo")).isFalse(); + assertThat(foo1.hashCode()).isEqualTo(foo1.hashCode()); + assertThat(foo1.hashCode()).isEqualTo(foo2.hashCode()); + } + + @Test + public void default_source_encoding() { + DefaultModuleFileSystem fs = new DefaultModuleFileSystem(moduleInputFileCache, + new Project("foo"), settings, fileIndexer, initializer, componentIndexer, mode); + + assertThat(fs.sourceCharset()).isEqualTo(Charset.defaultCharset()); + assertThat(fs.isDefaultJvmEncoding()).isTrue(); + } + + @Test + public void source_encoding_is_set() { + settings.setProperty(CoreProperties.ENCODING_PROPERTY, "Cp1124"); + DefaultModuleFileSystem fs = new DefaultModuleFileSystem(moduleInputFileCache, + new Project("foo"), settings, fileIndexer, initializer, componentIndexer, mode); + + assertThat(fs.encoding()).isEqualTo(Charset.forName("Cp1124")); + assertThat(fs.sourceCharset()).isEqualTo(Charset.forName("Cp1124")); + + // This test fails when default Java encoding is "IBM AIX Ukraine". Sorry for that. + assertThat(fs.isDefaultJvmEncoding()).isFalse(); + } + + @Test + public void default_predicate_scan_only_changed() throws IOException { + when(mode.scanAllFiles()).thenReturn(false); + + DefaultModuleFileSystem fs = new DefaultModuleFileSystem(moduleInputFileCache, + new Project("foo"), settings, fileIndexer, initializer, componentIndexer, mode); + + File baseDir = temp.newFile(); + InputFile mainInput = new DefaultInputFile("foo", "Main.java").setModuleBaseDir(baseDir.toPath()).setType(InputFile.Type.MAIN); + InputFile testInput = new DefaultInputFile("foo", "Test.java").setModuleBaseDir(baseDir.toPath()).setType(InputFile.Type.TEST); + InputFile mainSameInput = new DefaultInputFile("foo", "MainSame.java").setModuleBaseDir(baseDir.toPath()) + .setType(InputFile.Type.TEST).setStatus(Status.SAME); + when(moduleInputFileCache.inputFiles()).thenReturn(Lists.newArrayList(mainInput, testInput, mainSameInput)); + + fs.index(); + Iterable<InputFile> inputFiles = fs.inputFiles(fs.predicates().all()); + assertThat(inputFiles).containsOnly(mainInput, testInput); + + Iterable<InputFile> allInputFiles = fs.inputFiles(); + assertThat(allInputFiles).containsOnly(mainInput, mainSameInput, testInput); + } + + @Test + public void test_dirs() throws IOException { + File basedir = temp.newFolder("base"); + File buildDir = temp.newFolder("build"); + File workingDir = temp.newFolder("work"); + File additionalFile = temp.newFile("Main.java"); + File additionalTest = temp.newFile("Test.java"); + when(initializer.baseDir()).thenReturn(basedir); + when(initializer.buildDir()).thenReturn(buildDir); + when(initializer.workingDir()).thenReturn(workingDir); + when(initializer.binaryDirs()).thenReturn(Arrays.asList(new File(basedir, "target/classes"))); + File javaSrc = new File(basedir, "src/main/java"); + javaSrc.mkdirs(); + File groovySrc = new File(basedir, "src/main/groovy"); + groovySrc.mkdirs(); + when(initializer.sources()).thenReturn(Arrays.asList(javaSrc, groovySrc, additionalFile)); + File javaTest = new File(basedir, "src/test/java"); + javaTest.mkdirs(); + when(initializer.tests()).thenReturn(Arrays.asList(javaTest, additionalTest)); + + DefaultModuleFileSystem fs = new DefaultModuleFileSystem(moduleInputFileCache, + new Project("foo"), settings, fileIndexer, initializer, componentIndexer, mode); + + assertThat(fs.baseDir().getCanonicalPath()).isEqualTo(basedir.getCanonicalPath()); + assertThat(fs.workDir().getCanonicalPath()).isEqualTo(workingDir.getCanonicalPath()); + assertThat(fs.buildDir().getCanonicalPath()).isEqualTo(buildDir.getCanonicalPath()); + assertThat(fs.sourceDirs()).hasSize(2); + assertThat(fs.testDirs()).hasSize(1); + assertThat(fs.binaryDirs()).hasSize(1); + } + + @Test + public void should_search_input_files() throws Exception { + DefaultModuleFileSystem fs = new DefaultModuleFileSystem(moduleInputFileCache, + new Project("foo"), settings, fileIndexer, initializer, componentIndexer, mode); + + File baseDir = temp.newFile(); + InputFile mainInput = new DefaultInputFile("foo", "Main.java").setModuleBaseDir(baseDir.toPath()).setType(InputFile.Type.MAIN); + InputFile testInput = new DefaultInputFile("foo", "Test.java").setModuleBaseDir(baseDir.toPath()).setType(InputFile.Type.TEST); + when(moduleInputFileCache.inputFiles()).thenReturn(Lists.newArrayList(mainInput, testInput)); + + fs.index(); + Iterable<InputFile> inputFiles = fs.inputFiles(fs.predicates().hasType(InputFile.Type.MAIN)); + assertThat(inputFiles).containsOnly(mainInput); + + Iterable<File> files = fs.files(fs.predicates().hasType(InputFile.Type.MAIN)); + assertThat(files).containsOnly(new File(baseDir, "Main.java")); + } + + @Test + public void should_index() { + DefaultModuleFileSystem fs = new DefaultModuleFileSystem(moduleInputFileCache, + new Project("foo"), settings, fileIndexer, initializer, componentIndexer, mode); + + verifyZeroInteractions(fileIndexer); + + fs.index(); + verify(fileIndexer).index(fs); + verify(componentIndexer).execute(fs); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/DeprecatedFileFiltersTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/DeprecatedFileFiltersTest.java new file mode 100644 index 00000000000..e1905a12a9e --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/DeprecatedFileFiltersTest.java @@ -0,0 +1,77 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.apache.commons.io.FilenameUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.mockito.ArgumentCaptor; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.scan.filesystem.FileSystemFilter; +import org.sonar.api.scan.filesystem.FileType; + +import java.io.File; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class DeprecatedFileFiltersTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + FileSystemFilter filter = mock(FileSystemFilter.class); + + @Test + public void no_filters() { + DeprecatedFileFilters filters = new DeprecatedFileFilters(); + + InputFile inputFile = new DefaultInputFile("foo", "src/main/java/Foo.java"); + assertThat(filters.accept(inputFile)).isTrue(); + } + + @Test + public void at_least_one_filter() throws Exception { + DeprecatedFileFilters filters = new DeprecatedFileFilters(new FileSystemFilter[] {filter}); + + File basedir = temp.newFolder(); + File file = new File(basedir, "src/main/java/Foo.java"); + InputFile inputFile = new DefaultInputFile("foo", "src/main/java/Foo.java") + .setModuleBaseDir(basedir.toPath()) + .setType(InputFile.Type.MAIN); + when(filter.accept(eq(file), any(DeprecatedFileFilters.DeprecatedContext.class))).thenReturn(false); + + assertThat(filters.accept(inputFile)).isFalse(); + + ArgumentCaptor<DeprecatedFileFilters.DeprecatedContext> argument = ArgumentCaptor.forClass(DeprecatedFileFilters.DeprecatedContext.class); + verify(filter).accept(eq(file), argument.capture()); + + DeprecatedFileFilters.DeprecatedContext context = argument.getValue(); + assertThat(context.canonicalPath()).isEqualTo(FilenameUtils.separatorsToUnix(file.getAbsolutePath())); + assertThat(context.relativePath()).isEqualTo("src/main/java/Foo.java"); + assertThat(context.type()).isEqualTo(FileType.MAIN); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/ExclusionFiltersTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/ExclusionFiltersTest.java new file mode 100644 index 00000000000..c7b4e6eaa5e --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/ExclusionFiltersTest.java @@ -0,0 +1,134 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.config.Settings; +import org.sonar.api.scan.filesystem.FileExclusions; + +import java.io.File; +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ExclusionFiltersTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void no_inclusions_nor_exclusions() throws IOException { + ExclusionFilters filter = new ExclusionFilters(new FileExclusions(new Settings())); + filter.prepare(); + + java.io.File file = temp.newFile(); + DefaultInputFile inputFile = new DefaultInputFile("foo", "src/main/java/com/mycompany/FooDao.java").setModuleBaseDir(temp.newFolder().toPath()); + assertThat(filter.accept(inputFile, InputFile.Type.MAIN)).isTrue(); + assertThat(filter.accept(inputFile, InputFile.Type.TEST)).isTrue(); + } + + @Test + public void match_inclusion() throws IOException { + Settings settings = new Settings(); + settings.setProperty(CoreProperties.PROJECT_INCLUSIONS_PROPERTY, "**/*Dao.java"); + ExclusionFilters filter = new ExclusionFilters(new FileExclusions(settings)); + filter.prepare(); + + java.io.File file = temp.newFile(); + DefaultInputFile inputFile = new DefaultInputFile("foo", "src/main/java/com/mycompany/FooDao.java").setModuleBaseDir(temp.newFolder().toPath()); + assertThat(filter.accept(inputFile, InputFile.Type.MAIN)).isTrue(); + + inputFile = new DefaultInputFile("foo", "src/main/java/com/mycompany/Foo.java").setModuleBaseDir(temp.newFolder().toPath()); + assertThat(filter.accept(inputFile, InputFile.Type.MAIN)).isFalse(); + } + + @Test + public void match_at_least_one_inclusion() throws IOException { + Settings settings = new Settings(); + settings.setProperty(CoreProperties.PROJECT_INCLUSIONS_PROPERTY, "**/*Dao.java,**/*Dto.java"); + ExclusionFilters filter = new ExclusionFilters(new FileExclusions(settings)); + + filter.prepare(); + + java.io.File file = temp.newFile(); + + DefaultInputFile inputFile = new DefaultInputFile("foo", "src/main/java/com/mycompany/Foo.java").setModuleBaseDir(temp.newFolder().toPath()); + assertThat(filter.accept(inputFile, InputFile.Type.MAIN)).isFalse(); + + inputFile = new DefaultInputFile("foo", "src/main/java/com/mycompany/FooDto.java").setModuleBaseDir(temp.newFolder().toPath()); + assertThat(filter.accept(inputFile, InputFile.Type.MAIN)).isTrue(); + } + + @Test + public void match_exclusions() throws IOException { + Settings settings = new Settings(); + settings.setProperty(CoreProperties.PROJECT_INCLUSIONS_PROPERTY, "src/main/java/**/*"); + settings.setProperty(CoreProperties.PROJECT_TEST_INCLUSIONS_PROPERTY, "src/test/java/**/*"); + settings.setProperty(CoreProperties.PROJECT_EXCLUSIONS_PROPERTY, "**/*Dao.java"); + ExclusionFilters filter = new ExclusionFilters(new FileExclusions(settings)); + + filter.prepare(); + + java.io.File file = temp.newFile(); + DefaultInputFile inputFile = new DefaultInputFile("foo", "src/main/java/com/mycompany/FooDao.java").setModuleBaseDir(temp.newFolder().toPath()); + assertThat(filter.accept(inputFile, InputFile.Type.MAIN)).isFalse(); + + inputFile = new DefaultInputFile("foo", "src/main/java/com/mycompany/Foo.java").setModuleBaseDir(temp.newFolder().toPath()); + assertThat(filter.accept(inputFile, InputFile.Type.MAIN)).isTrue(); + + // source exclusions do not apply to tests + inputFile = new DefaultInputFile("foo", "src/test/java/com/mycompany/FooDao.java").setModuleBaseDir(temp.newFolder().toPath()); + assertThat(filter.accept(inputFile, InputFile.Type.TEST)).isTrue(); + } + + @Test + public void match_exclusion_by_absolute_path() throws IOException { + File baseDir = temp.newFile(); + File excludedFile = new File(baseDir, "src/main/java/org/bar/Bar.java"); + + Settings settings = new Settings(); + settings.setProperty(CoreProperties.PROJECT_INCLUSIONS_PROPERTY, "src/main/java/**/*"); + settings.setProperty(CoreProperties.PROJECT_EXCLUSIONS_PROPERTY, "file:" + excludedFile.getCanonicalPath()); + ExclusionFilters filter = new ExclusionFilters(new FileExclusions(settings)); + + filter.prepare(); + + DefaultInputFile inputFile = new DefaultInputFile("foo", "src/main/java/org/bar/Foo.java").setModuleBaseDir(baseDir.toPath()); + assertThat(filter.accept(inputFile, InputFile.Type.MAIN)).isTrue(); + + inputFile = new DefaultInputFile("foo", "src/main/java/org/bar/Bar.java").setModuleBaseDir(baseDir.toPath()); + assertThat(filter.accept(inputFile, InputFile.Type.MAIN)).isFalse(); + } + + @Test + public void trim_pattern() { + Settings settings = new Settings(); + settings.setProperty(CoreProperties.PROJECT_EXCLUSIONS_PROPERTY, " **/*Dao.java "); + ExclusionFilters filter = new ExclusionFilters(new FileExclusions(settings)); + + assertThat(filter.prepareMainExclusions()[0].toString()).isEqualTo("**/*Dao.java"); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/InputFileBuilderFactoryTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/InputFileBuilderFactoryTest.java new file mode 100644 index 00000000000..b9c627e1ea8 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/InputFileBuilderFactoryTest.java @@ -0,0 +1,49 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.junit.Test; +import org.mockito.Mockito; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.fs.internal.FileMetadata; +import org.sonar.api.config.Settings; +import org.sonar.api.scan.filesystem.PathResolver; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class InputFileBuilderFactoryTest { + @Test + public void create_builder() { + PathResolver pathResolver = new PathResolver(); + LanguageDetectionFactory langDetectionFactory = mock(LanguageDetectionFactory.class, Mockito.RETURNS_MOCKS); + StatusDetectionFactory statusDetectionFactory = mock(StatusDetectionFactory.class, Mockito.RETURNS_MOCKS); + DefaultModuleFileSystem fs = mock(DefaultModuleFileSystem.class); + + InputFileBuilderFactory factory = new InputFileBuilderFactory(ProjectDefinition.create().setKey("struts"), pathResolver, langDetectionFactory, + statusDetectionFactory, new Settings(), new FileMetadata()); + InputFileBuilder builder = factory.create(fs); + + assertThat(builder.langDetection()).isNotNull(); + assertThat(builder.statusDetection()).isNotNull(); + assertThat(builder.pathResolver()).isSameAs(pathResolver); + assertThat(builder.fs()).isSameAs(fs); + assertThat(builder.moduleKey()).isEqualTo("struts"); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/InputFileBuilderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/InputFileBuilderTest.java new file mode 100644 index 00000000000..eaec22ca37a --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/InputFileBuilderTest.java @@ -0,0 +1,118 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.apache.commons.io.FileUtils; +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.DefaultInputFile; +import org.sonar.api.batch.fs.internal.FileMetadata; +import org.sonar.api.config.Settings; +import org.sonar.api.scan.filesystem.PathResolver; +import org.sonar.api.utils.PathUtils; + +import java.io.File; +import java.nio.charset.StandardCharsets; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class InputFileBuilderTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + LanguageDetection langDetection = mock(LanguageDetection.class); + StatusDetection statusDetection = mock(StatusDetection.class); + DefaultModuleFileSystem fs = mock(DefaultModuleFileSystem.class); + + @Test + public void complete_input_file() throws Exception { + // file system + File basedir = temp.newFolder(); + File srcFile = new File(basedir, "src/main/java/foo/Bar.java"); + FileUtils.touch(srcFile); + FileUtils.write(srcFile, "single line"); + when(fs.baseDir()).thenReturn(basedir); + when(fs.encoding()).thenReturn(StandardCharsets.UTF_8); + + // lang + when(langDetection.language(any(InputFile.class))).thenReturn("java"); + + // status + when(statusDetection.status("foo", "src/main/java/foo/Bar.java", "6c1d64c0b3555892fe7273e954f6fb5a")) + .thenReturn(InputFile.Status.ADDED); + + InputFileBuilder builder = new InputFileBuilder("struts", new PathResolver(), + langDetection, statusDetection, fs, new Settings(), new FileMetadata()); + DefaultInputFile inputFile = builder.create(srcFile); + builder.completeAndComputeMetadata(inputFile, InputFile.Type.MAIN); + + assertThat(inputFile.type()).isEqualTo(InputFile.Type.MAIN); + assertThat(inputFile.file()).isEqualTo(srcFile.getAbsoluteFile()); + assertThat(inputFile.absolutePath()).isEqualTo(PathUtils.sanitize(srcFile.getAbsolutePath())); + assertThat(inputFile.language()).isEqualTo("java"); + assertThat(inputFile.key()).isEqualTo("struts:src/main/java/foo/Bar.java"); + assertThat(inputFile.relativePath()).isEqualTo("src/main/java/foo/Bar.java"); + assertThat(inputFile.lines()).isEqualTo(1); + } + + @Test + public void return_null_if_file_outside_basedir() throws Exception { + // file system + File basedir = temp.newFolder(); + File otherDir = temp.newFolder(); + File srcFile = new File(otherDir, "src/main/java/foo/Bar.java"); + FileUtils.touch(srcFile); + when(fs.baseDir()).thenReturn(basedir); + + InputFileBuilder builder = new InputFileBuilder("struts", new PathResolver(), + langDetection, statusDetection, fs, new Settings(), new FileMetadata()); + DefaultInputFile inputFile = builder.create(srcFile); + + assertThat(inputFile).isNull(); + } + + @Test + public void return_null_if_language_not_detected() throws Exception { + // file system + File basedir = temp.newFolder(); + File srcFile = new File(basedir, "src/main/java/foo/Bar.java"); + FileUtils.touch(srcFile); + FileUtils.write(srcFile, "single line"); + when(fs.baseDir()).thenReturn(basedir); + when(fs.encoding()).thenReturn(StandardCharsets.UTF_8); + + // lang + when(langDetection.language(any(InputFile.class))).thenReturn(null); + + InputFileBuilder builder = new InputFileBuilder("struts", new PathResolver(), + langDetection, statusDetection, fs, new Settings(), new FileMetadata()); + DefaultInputFile inputFile = builder.create(srcFile); + inputFile = builder.completeAndComputeMetadata(inputFile, InputFile.Type.MAIN); + + assertThat(inputFile).isNull(); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/InputPathCacheTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/InputPathCacheTest.java new file mode 100644 index 00000000000..23269eef7d3 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/InputPathCacheTest.java @@ -0,0 +1,82 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.InputFile.Status; +import org.sonar.api.batch.fs.InputFile.Type; +import org.sonar.api.batch.fs.InputPath; +import org.sonar.api.batch.fs.internal.DefaultInputFile; + +import java.nio.charset.StandardCharsets; + +import static org.assertj.core.api.Assertions.assertThat; + +public class InputPathCacheTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Before + public void start() { + } + + @After + public void stop() { + } + + @Test + public void should_add_input_file() throws Exception { + InputPathCache cache = new InputPathCache(); + DefaultInputFile fooFile = new DefaultInputFile("foo", "src/main/java/Foo.java").setModuleBaseDir(temp.newFolder().toPath()); + cache.put("struts", fooFile); + cache.put("struts-core", new DefaultInputFile("foo", "src/main/java/Bar.java") + .setLanguage("bla") + .setType(Type.MAIN) + .setStatus(Status.ADDED) + .setLines(2) + .setCharset(StandardCharsets.UTF_8) + .setModuleBaseDir(temp.newFolder().toPath())); + + DefaultInputFile loadedFile = (DefaultInputFile) cache.getFile("struts-core", "src/main/java/Bar.java"); + assertThat(loadedFile.relativePath()).isEqualTo("src/main/java/Bar.java"); + assertThat(loadedFile.charset()).isEqualTo(StandardCharsets.UTF_8); + + assertThat(cache.filesByModule("struts")).hasSize(1); + assertThat(cache.filesByModule("struts-core")).hasSize(1); + assertThat(cache.allFiles()).hasSize(2); + for (InputPath inputPath : cache.allFiles()) { + assertThat(inputPath.relativePath()).startsWith("src/main/java/"); + } + + cache.remove("struts", fooFile); + assertThat(cache.allFiles()).hasSize(1); + + cache.removeModule("struts"); + assertThat(cache.filesByModule("struts")).hasSize(0); + assertThat(cache.filesByModule("struts-core")).hasSize(1); + assertThat(cache.allFiles()).hasSize(1); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/LanguageDetectionFactoryTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/LanguageDetectionFactoryTest.java new file mode 100644 index 00000000000..b30bb73036c --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/LanguageDetectionFactoryTest.java @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.junit.Test; +import org.sonar.api.config.Settings; +import org.sonar.api.resources.Java; +import org.sonar.api.resources.Languages; +import org.sonar.batch.repository.language.DefaultLanguagesRepository; +import org.sonar.batch.repository.language.LanguagesRepository; + +import static org.assertj.core.api.Assertions.assertThat; + +public class LanguageDetectionFactoryTest { + @Test + public void testCreate() throws Exception { + LanguagesRepository languages = new DefaultLanguagesRepository(new Languages(Java.INSTANCE)); + LanguageDetectionFactory factory = new LanguageDetectionFactory(new Settings(), languages); + LanguageDetection languageDetection = factory.create(); + assertThat(languageDetection).isNotNull(); + assertThat(languageDetection.patternsByLanguage()).hasSize(1); + assertThat(languageDetection.patternsByLanguage().containsKey("java")).isTrue(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/LanguageDetectionTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/LanguageDetectionTest.java new file mode 100644 index 00000000000..01a6a8aef3c --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/LanguageDetectionTest.java @@ -0,0 +1,213 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.config.Settings; +import org.sonar.api.resources.Language; +import org.sonar.api.resources.Languages; +import org.sonar.api.utils.MessageException; +import org.sonar.batch.repository.language.DefaultLanguagesRepository; +import org.sonar.batch.repository.language.LanguagesRepository; + +import java.io.File; +import java.io.IOException; + +import static junit.framework.Assert.fail; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.spy; + +public class LanguageDetectionTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void test_sanitizeExtension() throws Exception { + assertThat(LanguageDetection.sanitizeExtension(".cbl")).isEqualTo("cbl"); + assertThat(LanguageDetection.sanitizeExtension(".CBL")).isEqualTo("cbl"); + assertThat(LanguageDetection.sanitizeExtension("CBL")).isEqualTo("cbl"); + assertThat(LanguageDetection.sanitizeExtension("cbl")).isEqualTo("cbl"); + } + + @Test + public void search_by_file_extension() throws Exception { + LanguagesRepository languages = new DefaultLanguagesRepository(new Languages(new MockLanguage("java", "java", "jav"), new MockLanguage("cobol", "cbl", "cob"))); + LanguageDetection detection = new LanguageDetection(new Settings(), languages); + + assertThat(detection.language(newInputFile("Foo.java"))).isEqualTo("java"); + assertThat(detection.language(newInputFile("src/Foo.java"))).isEqualTo("java"); + assertThat(detection.language(newInputFile("Foo.JAVA"))).isEqualTo("java"); + assertThat(detection.language(newInputFile("Foo.jav"))).isEqualTo("java"); + assertThat(detection.language(newInputFile("Foo.Jav"))).isEqualTo("java"); + + assertThat(detection.language(newInputFile("abc.cbl"))).isEqualTo("cobol"); + assertThat(detection.language(newInputFile("abc.CBL"))).isEqualTo("cobol"); + + assertThat(detection.language(newInputFile("abc.php"))).isNull(); + assertThat(detection.language(newInputFile("abc"))).isNull(); + } + + @Test + public void should_not_fail_if_no_language() throws Exception { + LanguageDetection detection = spy(new LanguageDetection(new Settings(), new DefaultLanguagesRepository(new Languages()))); + assertThat(detection.language(newInputFile("Foo.java"))).isNull(); + } + + @Test + public void plugin_can_declare_a_file_extension_twice_for_case_sensitivity() throws Exception { + LanguagesRepository languages = new DefaultLanguagesRepository(new Languages(new MockLanguage("abap", "abap", "ABAP"))); + + LanguageDetection detection = new LanguageDetection(new Settings(), languages); + assertThat(detection.language(newInputFile("abc.abap"))).isEqualTo("abap"); + } + + @Test + public void language_with_no_extension() throws Exception { + // abap does not declare any file extensions. + // When analyzing an ABAP project, then all source files must be parsed. + LanguagesRepository languages = new DefaultLanguagesRepository(new Languages(new MockLanguage("java", "java"), new MockLanguage("abap"))); + + // No side-effect on non-ABAP projects + LanguageDetection detection = new LanguageDetection(new Settings(), languages); + assertThat(detection.language(newInputFile("abc"))).isNull(); + assertThat(detection.language(newInputFile("abc.abap"))).isNull(); + assertThat(detection.language(newInputFile("abc.java"))).isEqualTo("java"); + + Settings settings = new Settings(); + settings.setProperty(CoreProperties.PROJECT_LANGUAGE_PROPERTY, "abap"); + detection = new LanguageDetection(settings, languages); + assertThat(detection.language(newInputFile("abc"))).isEqualTo("abap"); + assertThat(detection.language(newInputFile("abc.txt"))).isEqualTo("abap"); + assertThat(detection.language(newInputFile("abc.java"))).isEqualTo("abap"); + } + + @Test + public void force_language_using_deprecated_property() throws Exception { + LanguagesRepository languages = new DefaultLanguagesRepository(new Languages(new MockLanguage("java", "java"), new MockLanguage("php", "php"))); + + Settings settings = new Settings(); + settings.setProperty(CoreProperties.PROJECT_LANGUAGE_PROPERTY, "java"); + LanguageDetection detection = new LanguageDetection(settings, languages); + assertThat(detection.language(newInputFile("abc"))).isNull(); + assertThat(detection.language(newInputFile("abc.php"))).isNull(); + assertThat(detection.language(newInputFile("abc.java"))).isEqualTo("java"); + assertThat(detection.language(newInputFile("src/abc.java"))).isEqualTo("java"); + } + + @Test + public void fail_if_invalid_language() { + thrown.expect(MessageException.class); + thrown.expectMessage("No language is installed with key 'unknown'. Please update property 'sonar.language'"); + + LanguagesRepository languages = new DefaultLanguagesRepository(new Languages(new MockLanguage("java", "java"), new MockLanguage("php", "php"))); + Settings settings = new Settings(); + settings.setProperty(CoreProperties.PROJECT_LANGUAGE_PROPERTY, "unknown"); + new LanguageDetection(settings, languages); + } + + @Test + public void fail_if_conflicting_language_suffix() throws Exception { + LanguagesRepository languages = new DefaultLanguagesRepository(new Languages(new MockLanguage("xml", "xhtml"), new MockLanguage("web", "xhtml"))); + LanguageDetection detection = new LanguageDetection(new Settings(), languages); + try { + detection.language(newInputFile("abc.xhtml")); + fail(); + } catch (MessageException e) { + assertThat(e.getMessage()) + .contains("Language of file 'abc.xhtml' can not be decided as the file matches patterns of both ") + .contains("sonar.lang.patterns.web : **/*.xhtml") + .contains("sonar.lang.patterns.xml : **/*.xhtml"); + } + } + + @Test + public void solve_conflict_using_filepattern() throws Exception { + LanguagesRepository languages = new DefaultLanguagesRepository(new Languages(new MockLanguage("xml", "xhtml"), new MockLanguage("web", "xhtml"))); + + Settings settings = new Settings(); + settings.setProperty("sonar.lang.patterns.xml", "xml/**"); + settings.setProperty("sonar.lang.patterns.web", "web/**"); + LanguageDetection detection = new LanguageDetection(settings, languages); + assertThat(detection.language(newInputFile("xml/abc.xhtml"))).isEqualTo("xml"); + assertThat(detection.language(newInputFile("web/abc.xhtml"))).isEqualTo("web"); + } + + @Test + public void fail_if_conflicting_filepattern() throws Exception { + LanguagesRepository languages = new DefaultLanguagesRepository(new Languages(new MockLanguage("abap", "abap"), new MockLanguage("cobol", "cobol"))); + Settings settings = new Settings(); + settings.setProperty("sonar.lang.patterns.abap", "*.abap,*.txt"); + settings.setProperty("sonar.lang.patterns.cobol", "*.cobol,*.txt"); + + LanguageDetection detection = new LanguageDetection(settings, languages); + + assertThat(detection.language(newInputFile("abc.abap"))).isEqualTo("abap"); + assertThat(detection.language(newInputFile("abc.cobol"))).isEqualTo("cobol"); + try { + detection.language(newInputFile("abc.txt")); + fail(); + } catch (MessageException e) { + assertThat(e.getMessage()) + .contains("Language of file 'abc.txt' can not be decided as the file matches patterns of both ") + .contains("sonar.lang.patterns.abap : *.abap,*.txt") + .contains("sonar.lang.patterns.cobol : *.cobol,*.txt"); + } + } + + private InputFile newInputFile(String path) throws IOException { + File basedir = temp.newFolder(); + return new DefaultInputFile("foo", path).setModuleBaseDir(basedir.toPath()); + } + + static class MockLanguage implements Language { + private final String key; + private final String[] extensions; + + MockLanguage(String key, String... extensions) { + this.key = key; + this.extensions = extensions; + } + + @Override + public String getKey() { + return key; + } + + @Override + public String getName() { + return key; + } + + @Override + public String[] getFileSuffixes() { + return extensions; + } + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/ModuleFileSystemInitializerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/ModuleFileSystemInitializerTest.java new file mode 100644 index 00000000000..08dcf9197e5 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/ModuleFileSystemInitializerTest.java @@ -0,0 +1,92 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.scan.filesystem.PathResolver; +import org.sonar.api.utils.TempFolder; + +import java.io.File; +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class ModuleFileSystemInitializerTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + PathResolver pathResolver = new PathResolver(); + + @Test + public void test_default_directories() throws Exception { + File baseDir = temp.newFolder("base"); + File workDir = temp.newFolder("work"); + ProjectDefinition module = ProjectDefinition.create().setBaseDir(baseDir).setWorkDir(workDir); + + ModuleFileSystemInitializer initializer = new ModuleFileSystemInitializer(module, mock(TempFolder.class), pathResolver); + + assertThat(initializer.baseDir().getCanonicalPath()).isEqualTo(baseDir.getCanonicalPath()); + assertThat(initializer.workingDir().getCanonicalPath()).isEqualTo(workDir.getCanonicalPath()); + assertThat(initializer.sources()).isEmpty(); + assertThat(initializer.tests()).isEmpty(); + } + + @Test + public void should_init_directories() throws IOException { + File baseDir = temp.newFolder("base"); + File buildDir = temp.newFolder("build"); + File sourceDir = new File(baseDir, "src/main/java"); + FileUtils.forceMkdir(sourceDir); + File testDir = new File(baseDir, "src/test/java"); + FileUtils.forceMkdir(testDir); + File binaryDir = new File(baseDir, "target/classes"); + FileUtils.forceMkdir(binaryDir); + + ProjectDefinition project = ProjectDefinition.create() + .setBaseDir(baseDir) + .setBuildDir(buildDir) + .addSourceDirs("src/main/java", "src/main/unknown") + .addTestDirs("src/test/java", "src/test/unknown") + .addBinaryDir("target/classes"); + + ModuleFileSystemInitializer initializer = new ModuleFileSystemInitializer(project, mock(TempFolder.class), pathResolver); + + assertThat(initializer.baseDir().getCanonicalPath()).isEqualTo(baseDir.getCanonicalPath()); + assertThat(initializer.buildDir().getCanonicalPath()).isEqualTo(buildDir.getCanonicalPath()); + assertThat(initializer.sources()).hasSize(1); + assertThat(path(initializer.sources().get(0))).endsWith("src/main/java"); + assertThat(initializer.tests()).hasSize(1); + assertThat(path(initializer.tests().get(0))).endsWith("src/test/java"); + assertThat(initializer.binaryDirs()).hasSize(1); + assertThat(path(initializer.binaryDirs().get(0))).endsWith("target/classes"); + } + + private String path(File f) throws IOException { + return FilenameUtils.separatorsToUnix(f.getCanonicalPath()); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/StatusDetectionFactoryTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/StatusDetectionFactoryTest.java new file mode 100644 index 00000000000..b741f1eb344 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/StatusDetectionFactoryTest.java @@ -0,0 +1,35 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.sonar.batch.repository.ProjectRepositories; + +import org.junit.Test; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class StatusDetectionFactoryTest { + @Test + public void testCreate() throws Exception { + StatusDetectionFactory factory = new StatusDetectionFactory(mock(ProjectRepositories.class)); + StatusDetection detection = factory.create(); + assertThat(detection).isNotNull(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/StatusDetectionTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/StatusDetectionTest.java new file mode 100644 index 00000000000..0601fa26b3b --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/filesystem/StatusDetectionTest.java @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.filesystem; + +import org.sonar.batch.repository.FileData; + +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.ImmutableTable; +import com.google.common.collect.Table; +import org.junit.Test; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.batch.repository.ProjectRepositories; +import static org.assertj.core.api.Assertions.assertThat; + +public class StatusDetectionTest { + @Test + public void detect_status() { + Table<String, String, String> t = ImmutableTable.of(); + ProjectRepositories ref = new ProjectRepositories(t, createTable(), null); + StatusDetection statusDetection = new StatusDetection(ref); + + assertThat(statusDetection.status("foo", "src/Foo.java", "ABCDE")).isEqualTo(InputFile.Status.SAME); + assertThat(statusDetection.status("foo", "src/Foo.java", "XXXXX")).isEqualTo(InputFile.Status.CHANGED); + assertThat(statusDetection.status("foo", "src/Other.java", "QWERT")).isEqualTo(InputFile.Status.ADDED); + } + + private static Table<String, String, FileData> createTable() { + Table<String, String, FileData> t = HashBasedTable.create(); + + t.put("foo", "src/Foo.java", new FileData("ABCDE", "12345789")); + t.put("foo", "src/Bar.java", new FileData("FGHIJ", "123456789")); + + return t; + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/measure/MeasureCacheTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/measure/MeasureCacheTest.java new file mode 100644 index 00000000000..61f21529547 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/measure/MeasureCacheTest.java @@ -0,0 +1,231 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.measure; + +import java.util.Date; +import java.util.Iterator; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.batch.measure.MetricFinder; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.Metric.Level; +import org.sonar.api.resources.Directory; +import org.sonar.api.resources.File; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.batch.index.AbstractCachesTest; +import org.sonar.batch.index.Cache.Entry; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class MeasureCacheTest extends AbstractCachesTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private MetricFinder metricFinder; + + private MeasureCache measureCache; + + @Before + public void start() { + super.start(); + metricFinder = mock(MetricFinder.class); + when(metricFinder.findByKey(CoreMetrics.NCLOC_KEY)).thenReturn(CoreMetrics.NCLOC); + measureCache = new MeasureCache(caches, metricFinder); + } + + @Test + public void should_add_measure() { + Project p = new Project("struts"); + + assertThat(measureCache.entries()).hasSize(0); + assertThat(measureCache.byResource(p)).hasSize(0); + + Measure m = new Measure(CoreMetrics.NCLOC, 1.0); + measureCache.put(p, m); + + assertThat(measureCache.contains(p, m)).isTrue(); + assertThat(measureCache.entries()).hasSize(1); + Iterator<Entry<Measure>> iterator = measureCache.entries().iterator(); + iterator.hasNext(); + Entry<Measure> next = iterator.next(); + assertThat(next.value()).isEqualTo(m); + assertThat(next.key()[0]).isEqualTo("struts"); + + assertThat(measureCache.byResource(p)).hasSize(1); + assertThat(measureCache.byResource(p).iterator().next()).isEqualTo(m); + } + + @Test + public void should_add_measure_with_big_data() { + Project p = new Project("struts"); + + assertThat(measureCache.entries()).hasSize(0); + + assertThat(measureCache.byResource(p)).hasSize(0); + + Measure m = new Measure(CoreMetrics.NCLOC, 1.0).setDate(new Date()); + m.setAlertText("foooooooooooooooooooooooooooooooooooo"); + StringBuilder data = new StringBuilder(); + for (int i = 0; i < 1_048_575; i++) { + data.append("a"); + } + + m.setData(data.toString()); + + measureCache.put(p, m); + + assertThat(measureCache.contains(p, m)).isTrue(); + assertThat(measureCache.entries()).hasSize(1); + Iterator<Entry<Measure>> iterator = measureCache.entries().iterator(); + iterator.hasNext(); + Entry<Measure> next = iterator.next(); + assertThat(next.value()).isEqualTo(m); + assertThat(next.key()[0]).isEqualTo("struts"); + + assertThat(measureCache.byResource(p)).hasSize(1); + assertThat(measureCache.byResource(p).iterator().next()).isEqualTo(m); + } + + /** + * This test fails with stock PersisitIt. + */ + @Test + public void should_add_measure_with_too_big_data_for_persistit_pre_patch() { + Project p = new Project("struts"); + + assertThat(measureCache.entries()).hasSize(0); + + assertThat(measureCache.byResource(p)).hasSize(0); + + Measure m = new Measure(CoreMetrics.NCLOC, 1.0).setDate(new Date()); + StringBuilder data = new StringBuilder(); + for (int i = 0; i < 500000; i++) { + data.append("some data"); + } + m.setData(data.toString()); + + measureCache.put(p, m); + + assertThat(measureCache.contains(p, m)).isTrue(); + assertThat(measureCache.entries()).hasSize(1); + Iterator<Entry<Measure>> iterator = measureCache.entries().iterator(); + iterator.hasNext(); + Entry<Measure> next = iterator.next(); + assertThat(next.value()).isEqualTo(m); + assertThat(next.key()[0]).isEqualTo("struts"); + + assertThat(measureCache.byResource(p)).hasSize(1); + assertThat(measureCache.byResource(p).iterator().next()).isEqualTo(m); + + } + + @Test + public void should_add_measure_with_too_big_data_for_persistit() { + Project p = new Project("struts"); + + assertThat(measureCache.entries()).hasSize(0); + + assertThat(measureCache.byResource(p)).hasSize(0); + + Measure m = new Measure(CoreMetrics.NCLOC, 1.0).setDate(new Date()); + StringBuilder data = new StringBuilder(64 * 1024 * 1024 + 1); + // Limit is 64Mo + for (int i = 0; i < (64 * 1024 * 1024 + 1); i++) { + data.append('a'); + } + m.setData(data.toString()); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Fail to put element in the cache measures"); + + measureCache.put(p, m); + } + + @Test + public void should_get_measures() { + Project p = new Project("struts"); + Resource dir = Directory.create("foo/bar").setEffectiveKey("struts:foo/bar"); + Resource file1 = Directory.create("foo/bar/File1.txt").setEffectiveKey("struts:foo/bar/File1.txt"); + Resource file2 = Directory.create("foo/bar/File2.txt").setEffectiveKey("struts:foo/bar/File2.txt"); + + assertThat(measureCache.entries()).hasSize(0); + + assertThat(measureCache.byResource(p)).hasSize(0); + assertThat(measureCache.byResource(dir)).hasSize(0); + + Measure mFile1 = new Measure(CoreMetrics.NCLOC, 1.0); + measureCache.put(file1, mFile1); + Measure mFile2 = new Measure(CoreMetrics.NCLOC, 3.0); + measureCache.put(file2, mFile2); + + assertThat(measureCache.entries()).hasSize(2); + assertThat(measureCache.byResource(p)).hasSize(0); + assertThat(measureCache.byResource(dir)).hasSize(0); + + Measure mDir = new Measure(CoreMetrics.NCLOC, 4.0); + measureCache.put(dir, mDir); + + assertThat(measureCache.entries()).hasSize(3); + assertThat(measureCache.byResource(p)).hasSize(0); + assertThat(measureCache.byResource(dir)).hasSize(1); + assertThat(measureCache.byResource(dir).iterator().next()).isEqualTo(mDir); + + Measure mProj = new Measure(CoreMetrics.NCLOC, 4.0); + measureCache.put(p, mProj); + + assertThat(measureCache.entries()).hasSize(4); + assertThat(measureCache.byResource(p)).hasSize(1); + assertThat(measureCache.byResource(p).iterator().next()).isEqualTo(mProj); + assertThat(measureCache.byResource(dir)).hasSize(1); + assertThat(measureCache.byResource(dir).iterator().next()).isEqualTo(mDir); + } + + @Test + public void test_measure_coder() throws Exception { + Resource file1 = File.create("foo/bar/File1.txt").setEffectiveKey("struts:foo/bar/File1.txt"); + + Measure measure = new Measure(CoreMetrics.NCLOC, 3.14); + measure.setData("data"); + measure.setAlertStatus(Level.ERROR); + measure.setAlertText("alert"); + measure.setDate(new Date()); + measure.setDescription("description"); + measure.setPersistenceMode(null); + measure.setPersonId(3); + measure.setUrl("http://foo"); + measure.setVariation1(11.0); + measure.setVariation2(12.0); + measure.setVariation3(13.0); + measure.setVariation4(14.0); + measure.setVariation5(15.0); + measureCache.put(file1, measure); + + Measure savedMeasure = measureCache.byResource(file1).iterator().next(); + assertThat(EqualsBuilder.reflectionEquals(measure, savedMeasure)).isTrue(); + + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/report/ConsoleReportTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/report/ConsoleReportTest.java new file mode 100644 index 00000000000..1a49bb94731 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/report/ConsoleReportTest.java @@ -0,0 +1,145 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.report; + +import javax.annotation.Nullable; + +import org.sonar.batch.issue.tracking.TrackedIssue; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.config.Settings; +import org.sonar.api.rule.Severity; +import org.sonar.api.utils.log.LogTester; +import org.sonar.batch.issue.IssueCache; +import org.sonar.batch.scan.filesystem.InputPathCache; + +import java.util.Arrays; +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ConsoleReportTest { + + @Rule + public LogTester logTester = new LogTester(); + + private Settings settings; + private IssueCache issueCache; + private InputPathCache inputPathCache; + private ConsoleReport report; + + @Before + public void prepare() { + settings = new Settings(); + issueCache = mock(IssueCache.class); + inputPathCache = mock(InputPathCache.class); + report = new ConsoleReport(settings, issueCache, inputPathCache); + } + + @Test + public void dontExecuteByDefault() { + report.execute(); + for (String log : logTester.logs()) { + assertThat(log).doesNotContain(ConsoleReport.HEADER); + } + } + + @Test + public void testNoFile() { + settings.setProperty(ConsoleReport.CONSOLE_REPORT_ENABLED_KEY, "true"); + when(inputPathCache.allFiles()).thenReturn(Collections.<InputFile>emptyList()); + when(issueCache.all()).thenReturn(Collections.<TrackedIssue>emptyList()); + report.execute(); + assertThat(getReportLog()).isEqualTo( + "\n\n------------- Issues Report -------------\n\n" + + " No file analyzed\n" + + "\n-------------------------------------------\n\n"); + } + + @Test + public void testNoNewIssue() { + settings.setProperty(ConsoleReport.CONSOLE_REPORT_ENABLED_KEY, "true"); + when(inputPathCache.allFiles()).thenReturn(Arrays.<InputFile>asList(new DefaultInputFile("foo", "src/Foo.php"))); + when(issueCache.all()).thenReturn(Arrays.asList(createIssue(false, null))); + report.execute(); + assertThat(getReportLog()).isEqualTo( + "\n\n------------- Issues Report -------------\n\n" + + " No new issue\n" + + "\n-------------------------------------------\n\n"); + } + + @Test + public void testOneNewIssue() { + settings.setProperty(ConsoleReport.CONSOLE_REPORT_ENABLED_KEY, "true"); + when(inputPathCache.allFiles()).thenReturn(Arrays.<InputFile>asList(new DefaultInputFile("foo", "src/Foo.php"))); + when(issueCache.all()).thenReturn(Arrays.asList(createIssue(true, Severity.BLOCKER))); + report.execute(); + assertThat(getReportLog()).isEqualTo( + "\n\n------------- Issues Report -------------\n\n" + + " +1 issue\n\n" + + " +1 blocker\n" + + "\n-------------------------------------------\n\n"); + } + + @Test + public void testOneNewIssuePerSeverity() { + settings.setProperty(ConsoleReport.CONSOLE_REPORT_ENABLED_KEY, "true"); + when(inputPathCache.allFiles()).thenReturn(Arrays.<InputFile>asList(new DefaultInputFile("foo", "src/Foo.php"))); + when(issueCache.all()).thenReturn(Arrays.asList( + createIssue(true, Severity.BLOCKER), + createIssue(true, Severity.CRITICAL), + createIssue(true, Severity.MAJOR), + createIssue(true, Severity.MINOR), + createIssue(true, Severity.INFO))); + report.execute(); + assertThat(getReportLog()).isEqualTo( + "\n\n------------- Issues Report -------------\n\n" + + " +5 issues\n\n" + + " +1 blocker\n" + + " +1 critical\n" + + " +1 major\n" + + " +1 minor\n" + + " +1 info\n" + + "\n-------------------------------------------\n\n"); + } + + private String getReportLog() { + for (String log : logTester.logs()) { + if (log.contains(ConsoleReport.HEADER)) { + return log; + } + } + throw new IllegalStateException("No console report"); + } + + private TrackedIssue createIssue(boolean isNew, @Nullable String severity) { + TrackedIssue issue = new TrackedIssue(); + issue.setNew(isNew); + issue.setSeverity(severity); + + return issue; + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/report/JSONReportTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/report/JSONReportTest.java new file mode 100644 index 00000000000..da798a2dbf1 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/report/JSONReportTest.java @@ -0,0 +1,174 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.report; + +import com.google.common.collect.Lists; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Collections; +import java.util.TimeZone; +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.InputDir; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultFileSystem; +import org.sonar.api.batch.fs.internal.DefaultInputDir; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.rule.Rules; +import org.sonar.api.batch.rule.internal.RulesBuilder; +import org.sonar.api.config.Settings; +import org.sonar.api.issue.Issue; +import org.sonar.api.platform.Server; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.api.rule.RuleKey; +import org.sonar.batch.issue.IssueCache; +import org.sonar.batch.issue.tracking.TrackedIssue; +import org.sonar.batch.repository.user.UserRepositoryLoader; +import org.sonar.batch.scan.filesystem.InputPathCache; +import org.sonar.scanner.protocol.input.ScannerInput; + +import static net.javacrumbs.jsonunit.assertj.JsonAssert.assertThatJson; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +public class JSONReportTest { + + private SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); + + @org.junit.Rule + public TemporaryFolder temp = new TemporaryFolder(); + + JSONReport jsonReport; + Resource resource = mock(Resource.class); + DefaultFileSystem fs; + Server server = mock(Server.class); + Rules rules = mock(Rules.class); + Settings settings = new Settings(); + IssueCache issueCache = mock(IssueCache.class); + private UserRepositoryLoader userRepository; + + @Before + public void before() throws Exception { + fs = new DefaultFileSystem(temp.newFolder().toPath()); + SIMPLE_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT+02:00")); + when(resource.getEffectiveKey()).thenReturn("Action.java"); + when(server.getVersion()).thenReturn("3.6"); + userRepository = mock(UserRepositoryLoader.class); + DefaultInputDir inputDir = new DefaultInputDir("struts", "src/main/java/org/apache/struts"); + DefaultInputFile inputFile = new DefaultInputFile("struts", "src/main/java/org/apache/struts/Action.java"); + inputFile.setStatus(InputFile.Status.CHANGED); + InputPathCache fileCache = mock(InputPathCache.class); + when(fileCache.allFiles()).thenReturn(Arrays.<InputFile>asList(inputFile)); + when(fileCache.allDirs()).thenReturn(Arrays.<InputDir>asList(inputDir)); + Project rootModule = new Project("struts"); + Project moduleA = new Project("struts-core"); + moduleA.setParent(rootModule).setPath("core"); + Project moduleB = new Project("struts-ui"); + moduleB.setParent(rootModule).setPath("ui"); + + RulesBuilder builder = new RulesBuilder(); + builder.add(RuleKey.of("squid", "AvoidCycles")).setName("Avoid Cycles"); + rules = builder.build(); + jsonReport = new JSONReport(settings, fs, server, rules, issueCache, rootModule, fileCache, userRepository); + } + + @Test + public void should_write_json() throws Exception { + TrackedIssue issue = new TrackedIssue(); + issue.setKey("200"); + issue.setComponentKey("struts:src/main/java/org/apache/struts/Action.java"); + issue.setRuleKey(RuleKey.of("squid", "AvoidCycles")); + issue.setMessage("There are 2 cycles"); + issue.setSeverity("MINOR"); + issue.setStatus(Issue.STATUS_OPEN); + issue.setResolution(null); + issue.setStartLine(1); + issue.setEndLine(2); + issue.setStartLineOffset(3); + issue.setEndLineOffset(4); + issue.setGap(3.14); + issue.setReporter("julien"); + issue.setAssignee("simon"); + issue.setCreationDate(SIMPLE_DATE_FORMAT.parse("2013-04-24")); + issue.setNew(false); + when(issueCache.all()).thenReturn(Lists.newArrayList(issue)); + ScannerInput.User user1 = ScannerInput.User.newBuilder().setLogin("julien").setName("Julien").build(); + ScannerInput.User user2 = ScannerInput.User.newBuilder().setLogin("simon").setName("Simon").build(); + when(userRepository.load("julien")).thenReturn(user1); + when(userRepository.load("simon")).thenReturn(user2); + + StringWriter writer = new StringWriter(); + jsonReport.writeJson(writer); + + assertThatJson(writer.toString()).isEqualTo(IOUtils.toString(this.getClass().getResource(this.getClass().getSimpleName() + "/report.json"))); + } + + @Test + public void should_exclude_resolved_issues() throws Exception { + RuleKey ruleKey = RuleKey.of("squid", "AvoidCycles"); + TrackedIssue issue = new TrackedIssue(); + issue.setKey("200"); + issue.setComponentKey("struts:src/main/java/org/apache/struts/Action.java"); + issue.setRuleKey(ruleKey); + issue.setStatus(Issue.STATUS_CLOSED); + issue.setResolution(Issue.RESOLUTION_FIXED); + issue.setCreationDate(SIMPLE_DATE_FORMAT.parse("2013-04-24")); + issue.setNew(false); + when(issueCache.all()).thenReturn(Lists.newArrayList(issue)); + + StringWriter writer = new StringWriter(); + jsonReport.writeJson(writer); + + assertThatJson(writer.toString()).isEqualTo(IOUtils.toString(this.getClass().getResource(this.getClass().getSimpleName() + "/report-without-resolved-issues.json"))); + } + + @Test + public void should_not_export_by_default() throws IOException { + File workDir = temp.newFolder("sonar"); + fs.setWorkDir(workDir); + + jsonReport.execute(); + + verifyZeroInteractions(issueCache); + } + + @Test + public void should_export_issues_to_file() throws IOException { + File workDir = temp.newFolder("sonar"); + fs.setWorkDir(workDir); + + when(issueCache.all()).thenReturn(Collections.<TrackedIssue>emptyList()); + + settings.setProperty("sonar.report.export.path", "output.json"); + + jsonReport.execute(); + + assertThat(new File(workDir, "output.json")).exists(); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/report/RuleNameProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/report/RuleNameProviderTest.java new file mode 100644 index 00000000000..3c3af0e689c --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scan/report/RuleNameProviderTest.java @@ -0,0 +1,67 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan.report; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.assertj.core.api.Assertions.assertThat; + +import static org.mockito.Matchers.any; + +import org.sonar.api.rule.RuleKey; +import org.sonar.api.batch.rule.Rule; +import org.junit.Test; +import org.junit.Before; +import org.sonar.api.batch.rule.Rules; + +public class RuleNameProviderTest { + RuleNameProvider provider; + Rules rules; + Rule rule; + RuleKey ruleKey; + + @Before + public void setUp() { + ruleKey = mock(RuleKey.class); + rule = mock(Rule.class); + rules = mock(Rules.class); + provider = new RuleNameProvider(rules); + + when(ruleKey.rule()).thenReturn("ruleKey"); + when(ruleKey.repository()).thenReturn("repoKey"); + + when(rule.name()).thenReturn("name"); + when(rule.key()).thenReturn(ruleKey); + + when(rules.find(any(RuleKey.class))).thenReturn(rule); + } + + @Test + public void testNameForHTML() { + assertThat(provider.nameForHTML(rule)).isEqualTo(rule.name()); + assertThat(provider.nameForHTML(ruleKey)).isEqualTo(rule.name()); + } + + @Test + public void testNameForJS() { + assertThat(provider.nameForJS("repoKey:ruleKey")).isEqualTo(rule.name()); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/scm/DefaultBlameOutputTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/scm/DefaultBlameOutputTest.java new file mode 100644 index 00000000000..743f9175ba4 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/scm/DefaultBlameOutputTest.java @@ -0,0 +1,94 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scm; + +import java.util.Arrays; +import java.util.Date; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.batch.fs.InputComponent; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.scm.BlameLine; +import org.sonar.batch.index.BatchComponent; +import org.sonar.batch.index.BatchComponentCache; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DefaultBlameOutputTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private BatchComponentCache componentCache; + + @Before + public void prepare() { + componentCache = mock(BatchComponentCache.class); + BatchComponent component = mock(BatchComponent.class); + when(component.batchId()).thenReturn(1); + when(componentCache.get(any(InputComponent.class))).thenReturn(component); + } + + @Test + public void shouldNotFailIfNotSameNumberOfLines() { + InputFile file = new DefaultInputFile("foo", "src/main/java/Foo.java").setLines(10); + + new DefaultBlameOutput(null, null, Arrays.asList(file)).blameResult(file, Arrays.asList(new BlameLine().revision("1").author("guy"))); + } + + @Test + public void shouldFailIfNotExpectedFile() { + InputFile file = new DefaultInputFile("foo", "src/main/java/Foo.java").setLines(1); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("It was not expected to blame file src/main/java/Foo.java"); + + new DefaultBlameOutput(null, null, Arrays.<InputFile>asList(new DefaultInputFile("foo", "src/main/java/Foo2.java"))) + .blameResult(file, Arrays.asList(new BlameLine().revision("1").author("guy"))); + } + + @Test + public void shouldFailIfNullDate() { + InputFile file = new DefaultInputFile("foo", "src/main/java/Foo.java").setLines(1); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Blame date is null for file src/main/java/Foo.java at line 1"); + + new DefaultBlameOutput(null, componentCache, Arrays.<InputFile>asList(file)) + .blameResult(file, Arrays.asList(new BlameLine().revision("1").author("guy"))); + } + + @Test + public void shouldFailIfNullRevision() { + InputFile file = new DefaultInputFile("foo", "src/main/java/Foo.java").setLines(1); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Blame revision is blank for file src/main/java/Foo.java at line 1"); + + new DefaultBlameOutput(null, componentCache, Arrays.<InputFile>asList(file)) + .blameResult(file, Arrays.asList(new BlameLine().date(new Date()).author("guy"))); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/sensor/DefaultSensorContextTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/sensor/DefaultSensorContextTest.java new file mode 100644 index 00000000000..ba40858840d --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/sensor/DefaultSensorContextTest.java @@ -0,0 +1,79 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.sensor; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.api.batch.fs.InputModule; +import org.sonar.api.batch.fs.internal.DefaultFileSystem; +import org.sonar.api.batch.measure.MetricFinder; +import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.batch.rule.internal.ActiveRulesBuilder; +import org.sonar.api.batch.sensor.internal.SensorStorage; +import org.sonar.api.config.Settings; +import org.sonar.api.measures.CoreMetrics; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DefaultSensorContextTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private ActiveRules activeRules; + private DefaultFileSystem fs; + private DefaultSensorContext adaptor; + private Settings settings; + private SensorStorage sensorStorage; + private AnalysisMode analysisMode; + + @Before + public void prepare() throws Exception { + activeRules = new ActiveRulesBuilder().build(); + fs = new DefaultFileSystem(temp.newFolder().toPath()); + MetricFinder metricFinder = mock(MetricFinder.class); + when(metricFinder.findByKey(CoreMetrics.NCLOC_KEY)).thenReturn(CoreMetrics.NCLOC); + when(metricFinder.findByKey(CoreMetrics.FUNCTION_COMPLEXITY_DISTRIBUTION_KEY)).thenReturn(CoreMetrics.FUNCTION_COMPLEXITY_DISTRIBUTION); + settings = new Settings(); + sensorStorage = mock(SensorStorage.class); + analysisMode = mock(AnalysisMode.class); + adaptor = new DefaultSensorContext(mock(InputModule.class), settings, fs, activeRules, analysisMode, sensorStorage); + } + + @Test + public void shouldProvideComponents() { + assertThat(adaptor.activeRules()).isEqualTo(activeRules); + assertThat(adaptor.fileSystem()).isEqualTo(fs); + assertThat(adaptor.settings()).isEqualTo(settings); + + assertThat(adaptor.newIssue()).isNotNull(); + assertThat(adaptor.newMeasure()).isNotNull(); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/sensor/DefaultSensorStorageTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/sensor/DefaultSensorStorageTest.java new file mode 100644 index 00000000000..caa16b11a19 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/sensor/DefaultSensorStorageTest.java @@ -0,0 +1,140 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.sensor; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.mockito.ArgumentCaptor; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultFileSystem; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.fs.internal.DefaultInputModule; +import org.sonar.api.batch.measure.MetricFinder; +import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.batch.rule.internal.ActiveRulesBuilder; +import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure; +import org.sonar.api.config.Settings; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.resources.File; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.batch.cpd.index.SonarCpdBlockIndex; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.batch.issue.ModuleIssues; +import org.sonar.batch.report.ReportPublisher; +import org.sonar.batch.scan.measure.MeasureCache; +import org.sonar.batch.sensor.coverage.CoverageExclusions; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DefaultSensorStorageTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private ActiveRules activeRules; + private DefaultFileSystem fs; + private DefaultSensorStorage sensorStorage; + private Settings settings; + private ModuleIssues moduleIssues; + private Project project; + private MeasureCache measureCache; + + private BatchComponentCache resourceCache; + + @Before + public void prepare() throws Exception { + activeRules = new ActiveRulesBuilder().build(); + fs = new DefaultFileSystem(temp.newFolder().toPath()); + MetricFinder metricFinder = mock(MetricFinder.class); + when(metricFinder.findByKey(CoreMetrics.NCLOC_KEY)).thenReturn(CoreMetrics.NCLOC); + when(metricFinder.findByKey(CoreMetrics.FUNCTION_COMPLEXITY_DISTRIBUTION_KEY)).thenReturn(CoreMetrics.FUNCTION_COMPLEXITY_DISTRIBUTION); + settings = new Settings(); + moduleIssues = mock(ModuleIssues.class); + project = new Project("myProject"); + measureCache = mock(MeasureCache.class); + CoverageExclusions coverageExclusions = mock(CoverageExclusions.class); + when(coverageExclusions.accept(any(Resource.class), any(Measure.class))).thenReturn(true); + resourceCache = new BatchComponentCache(); + sensorStorage = new DefaultSensorStorage(metricFinder, + moduleIssues, settings, fs, activeRules, coverageExclusions, resourceCache, mock(ReportPublisher.class), measureCache, mock(SonarCpdBlockIndex.class)); + } + + @Test + public void shouldFailIfUnknowMetric() { + InputFile file = new DefaultInputFile("foo", "src/Foo.php"); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Unknow metric with key: lines"); + + sensorStorage.store(new DefaultMeasure() + .on(file) + .forMetric(CoreMetrics.LINES) + .withValue(10)); + } + + @Test + public void shouldSaveFileMeasureToSensorContext() { + InputFile file = new DefaultInputFile("foo", "src/Foo.php"); + + ArgumentCaptor<org.sonar.api.measures.Measure> argumentCaptor = ArgumentCaptor.forClass(org.sonar.api.measures.Measure.class); + Resource sonarFile = File.create("src/Foo.php").setEffectiveKey("foo:src/Foo.php"); + resourceCache.add(sonarFile, null).setInputComponent(file); + when(measureCache.put(eq(sonarFile), argumentCaptor.capture())).thenReturn(null); + sensorStorage.store(new DefaultMeasure() + .on(file) + .forMetric(CoreMetrics.NCLOC) + .withValue(10)); + + org.sonar.api.measures.Measure m = argumentCaptor.getValue(); + assertThat(m.getValue()).isEqualTo(10.0); + assertThat(m.getMetric()).isEqualTo(CoreMetrics.NCLOC); + } + + @Test + public void shouldSaveProjectMeasureToSensorContext() { + DefaultInputModule module = new DefaultInputModule(project.getEffectiveKey()); + resourceCache.add(project, null).setInputComponent(module); + + ArgumentCaptor<org.sonar.api.measures.Measure> argumentCaptor = ArgumentCaptor.forClass(org.sonar.api.measures.Measure.class); + when(measureCache.put(eq(project), argumentCaptor.capture())).thenReturn(null); + + sensorStorage.store(new DefaultMeasure() + .on(module) + .forMetric(CoreMetrics.NCLOC) + .withValue(10)); + + org.sonar.api.measures.Measure m = argumentCaptor.getValue(); + assertThat(m.getValue()).isEqualTo(10.0); + assertThat(m.getMetric()).isEqualTo(CoreMetrics.NCLOC); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/sensor/SensorOptimizerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/sensor/SensorOptimizerTest.java new file mode 100644 index 00000000000..0fac9a72139 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/sensor/SensorOptimizerTest.java @@ -0,0 +1,136 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.sensor; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultFileSystem; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.batch.rule.internal.ActiveRulesBuilder; +import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; +import org.sonar.api.config.Settings; +import org.sonar.api.rule.RuleKey; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SensorOptimizerTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private DefaultFileSystem fs; + private SensorOptimizer optimizer; + private Settings settings; + + @Before + public void prepare() throws Exception { + fs = new DefaultFileSystem(temp.newFolder().toPath()); + settings = new Settings(); + optimizer = new SensorOptimizer(fs, new ActiveRulesBuilder().build(), settings); + } + + @Test + public void should_run_analyzer_with_no_metadata() { + DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor(); + + assertThat(optimizer.shouldExecute(descriptor)).isTrue(); + } + + @Test + public void should_optimize_on_language() { + DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor() + .onlyOnLanguages("java", "php"); + assertThat(optimizer.shouldExecute(descriptor)).isFalse(); + + fs.add(new DefaultInputFile("foo", "src/Foo.java").setLanguage("java")); + assertThat(optimizer.shouldExecute(descriptor)).isTrue(); + } + + @Test + public void should_optimize_on_type() { + DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor() + .onlyOnFileType(InputFile.Type.MAIN); + assertThat(optimizer.shouldExecute(descriptor)).isFalse(); + + fs.add(new DefaultInputFile("foo", "tests/FooTest.java").setType(InputFile.Type.TEST)); + assertThat(optimizer.shouldExecute(descriptor)).isFalse(); + + fs.add(new DefaultInputFile("foo", "src/Foo.java").setType(InputFile.Type.MAIN)); + assertThat(optimizer.shouldExecute(descriptor)).isTrue(); + } + + @Test + public void should_optimize_on_both_type_and_language() { + DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor() + .onlyOnLanguages("java", "php") + .onlyOnFileType(InputFile.Type.MAIN); + assertThat(optimizer.shouldExecute(descriptor)).isFalse(); + + fs.add(new DefaultInputFile("foo", "tests/FooTest.java").setLanguage("java").setType(InputFile.Type.TEST)); + fs.add(new DefaultInputFile("foo", "src/Foo.cbl").setLanguage("cobol").setType(InputFile.Type.MAIN)); + assertThat(optimizer.shouldExecute(descriptor)).isFalse(); + + fs.add(new DefaultInputFile("foo", "src/Foo.java").setLanguage("java").setType(InputFile.Type.MAIN)); + assertThat(optimizer.shouldExecute(descriptor)).isTrue(); + } + + @Test + public void should_optimize_on_repository() { + DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor() + .createIssuesForRuleRepositories("squid"); + assertThat(optimizer.shouldExecute(descriptor)).isFalse(); + + ActiveRules activeRules = new ActiveRulesBuilder() + .create(RuleKey.of("repo1", "foo")) + .activate() + .build(); + optimizer = new SensorOptimizer(fs, activeRules, settings); + + assertThat(optimizer.shouldExecute(descriptor)).isFalse(); + + activeRules = new ActiveRulesBuilder() + .create(RuleKey.of("repo1", "foo")) + .activate() + .create(RuleKey.of("squid", "rule")) + .activate() + .build(); + optimizer = new SensorOptimizer(fs, activeRules, settings); + assertThat(optimizer.shouldExecute(descriptor)).isTrue(); + } + + @Test + public void should_optimize_on_settings() { + DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor() + .requireProperty("sonar.foo.reportPath"); + assertThat(optimizer.shouldExecute(descriptor)).isFalse(); + + settings.setProperty("sonar.foo.reportPath", "foo"); + assertThat(optimizer.shouldExecute(descriptor)).isTrue(); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/sensor/coverage/CoverageExclusionsTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/sensor/coverage/CoverageExclusionsTest.java new file mode 100644 index 00000000000..284226f9af0 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/sensor/coverage/CoverageExclusionsTest.java @@ -0,0 +1,143 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.sensor.coverage; + +import org.junit.rules.TemporaryFolder; + +import org.sonar.api.batch.fs.internal.DefaultFileSystem; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import com.google.common.collect.ImmutableMap; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.config.PropertyDefinitions; +import org.sonar.api.config.Settings; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.resources.File; +import org.sonar.api.resources.Resource; +import org.sonar.api.utils.KeyValueFormat; +import org.sonar.core.config.ExclusionProperties; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class CoverageExclusionsTest { + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private Settings settings; + private DefaultFileSystem fs; + + private CoverageExclusions filter; + + @Before + public void createFilter() { + settings = new Settings(new PropertyDefinitions(ExclusionProperties.all())); + fs = new DefaultFileSystem(temp.getRoot()); + filter = new CoverageExclusions(settings, fs); + } + + @Test + public void shouldValidateStrictlyPositiveLine() { + DefaultInputFile file = new DefaultInputFile("module", "testfile"); + Measure measure = mock(Measure.class); + Map<Integer, Integer> map = ImmutableMap.of(0, 3); + + String data = KeyValueFormat.format(map); + when(measure.getMetric()).thenReturn(CoreMetrics.IT_CONDITIONS_BY_LINE); + when(measure.getData()).thenReturn(data); + + fs.add(file); + + exception.expect(IllegalStateException.class); + exception.expectMessage("must be > 0"); + filter.validate(measure, "testfile"); + } + + @Test + public void shouldValidateFileExists() { + DefaultInputFile file = new DefaultInputFile("module", "testfile"); + Measure measure = mock(Measure.class); + Map<Integer, Integer> map = ImmutableMap.of(0, 3); + + String data = KeyValueFormat.format(map); + when(measure.getMetric()).thenReturn(CoreMetrics.IT_CONDITIONS_BY_LINE); + when(measure.getData()).thenReturn(data); + + fs.add(file); + + exception.expect(IllegalStateException.class); + exception.expectMessage("resource is not indexed as a file"); + filter.validate(measure, "dummy"); + } + + @Test + public void shouldValidateMaxLine() { + DefaultInputFile file = new DefaultInputFile("module", "testfile"); + file.setLines(10); + Measure measure = mock(Measure.class); + Map<Integer, Integer> map = ImmutableMap.of(11, 3); + + String data = KeyValueFormat.format(map); + when(measure.getMetric()).thenReturn(CoreMetrics.COVERED_CONDITIONS_BY_LINE); + when(measure.getData()).thenReturn(data); + + exception.expect(IllegalStateException.class); + filter.validate(measure, file); + } + + @Test + public void shouldNotFilterNonCoverageMetrics() { + Measure otherMeasure = mock(Measure.class); + when(otherMeasure.getMetric()).thenReturn(CoreMetrics.LINES); + assertThat(filter.accept(mock(Resource.class), otherMeasure)).isTrue(); + } + + @Test + public void shouldFilterFileBasedOnPattern() { + Resource resource = File.create("src/org/polop/File.php", null, false); + Measure coverageMeasure = mock(Measure.class); + when(coverageMeasure.getMetric()).thenReturn(CoreMetrics.LINES_TO_COVER); + + settings.setProperty("sonar.coverage.exclusions", "src/org/polop/*"); + filter.initPatterns(); + assertThat(filter.accept(resource, coverageMeasure)).isFalse(); + } + + @Test + public void shouldNotFilterFileBasedOnPattern() { + Resource resource = File.create("src/org/polop/File.php", null, false); + Measure coverageMeasure = mock(Measure.class); + when(coverageMeasure.getMetric()).thenReturn(CoreMetrics.COVERAGE); + + settings.setProperty("sonar.coverage.exclusions", "src/org/other/*"); + filter.initPatterns(); + assertThat(filter.accept(resource, coverageMeasure)).isTrue(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/source/CodeColorizersTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/source/CodeColorizersTest.java new file mode 100644 index 00000000000..2a9530e1142 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/source/CodeColorizersTest.java @@ -0,0 +1,230 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.source; + +import com.google.common.collect.ImmutableList; +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.apache.commons.io.FileUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.fs.internal.FileMetadata; +import org.sonar.api.batch.sensor.highlighting.NewHighlighting; +import org.sonar.api.batch.sensor.highlighting.TypeOfText; +import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting; +import org.sonar.api.batch.sensor.internal.SensorStorage; +import org.sonar.api.web.CodeColorizerFormat; +import org.sonar.colorizer.CDocTokenizer; +import org.sonar.colorizer.CppDocTokenizer; +import org.sonar.colorizer.JavadocTokenizer; +import org.sonar.colorizer.KeywordsTokenizer; +import org.sonar.colorizer.MultilinesDocTokenizer; +import org.sonar.colorizer.RegexpTokenizer; +import org.sonar.colorizer.StringTokenizer; +import org.sonar.colorizer.Tokenizer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class CodeColorizersTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void testConvertToHighlighting() throws Exception { + CodeColorizers codeColorizers = new CodeColorizers(Arrays.<CodeColorizerFormat>asList(new JavaScriptColorizerFormat(), new WebCodeColorizerFormat())); + File jsFile = new File(this.getClass().getResource("CodeColorizersTest/Person.js").toURI()); + NewHighlighting highlighting = mock(NewHighlighting.class); + + codeColorizers.toSyntaxHighlighting(jsFile, StandardCharsets.UTF_8, "js", highlighting); + + verifyForJs(highlighting); + } + + private void verifyForJs(NewHighlighting highlighting) { + verify(highlighting).highlight(0, 4, TypeOfText.CPP_DOC); + verify(highlighting).highlight(5, 11, TypeOfText.CPP_DOC); + verify(highlighting).highlight(12, 15, TypeOfText.CPP_DOC); + verify(highlighting).highlight(16, 19, TypeOfText.KEYWORD); + verify(highlighting).highlight(29, 37, TypeOfText.KEYWORD); + verify(highlighting).highlight(65, 69, TypeOfText.KEYWORD); + verify(highlighting).highlight(85, 93, TypeOfText.COMMENT); + verify(highlighting).highlight(98, 102, TypeOfText.KEYWORD); + verify(highlighting).highlight(112, 114, TypeOfText.STRING); + verify(highlighting).highlight(120, 124, TypeOfText.KEYWORD); + } + + @Test + public void testConvertToHighlightingIgnoreBOM() throws Exception { + CodeColorizers codeColorizers = new CodeColorizers(Arrays.<CodeColorizerFormat>asList(new JavaScriptColorizerFormat(), new WebCodeColorizerFormat())); + + File fileWithBom = temp.newFile(); + FileUtils.write(fileWithBom, "\uFEFF", "UTF-8"); + File jsFile = new File(this.getClass().getResource("CodeColorizersTest/Person.js").toURI()); + FileUtils.write(fileWithBom, FileUtils.readFileToString(jsFile), "UTF-8", true); + + NewHighlighting highlighting = mock(NewHighlighting.class); + codeColorizers.toSyntaxHighlighting(fileWithBom, StandardCharsets.UTF_8, "js", highlighting); + + verifyForJs(highlighting); + } + + @Test + public void shouldSupportJavaIfNotProvidedByJavaPluginForBackwardCompatibility() throws Exception { + CodeColorizers codeColorizers = new CodeColorizers(Arrays.<CodeColorizerFormat>asList()); + + File javaFile = new File(this.getClass().getResource("CodeColorizersTest/Person.java").toURI()); + + NewHighlighting highlighting = mock(NewHighlighting.class); + codeColorizers.toSyntaxHighlighting(javaFile, StandardCharsets.UTF_8, "java", highlighting); + + verify(highlighting).highlight(0, 4, TypeOfText.STRUCTURED_COMMENT); + verify(highlighting).highlight(5, 11, TypeOfText.STRUCTURED_COMMENT); + verify(highlighting).highlight(12, 15, TypeOfText.STRUCTURED_COMMENT); + verify(highlighting).highlight(16, 22, TypeOfText.KEYWORD); + verify(highlighting).highlight(23, 28, TypeOfText.KEYWORD); + verify(highlighting).highlight(43, 50, TypeOfText.KEYWORD); + verify(highlighting).highlight(51, 54, TypeOfText.KEYWORD); + verify(highlighting).highlight(67, 78, TypeOfText.ANNOTATION); + verify(highlighting).highlight(81, 87, TypeOfText.KEYWORD); + verify(highlighting).highlight(88, 92, TypeOfText.KEYWORD); + verify(highlighting).highlight(97, 100, TypeOfText.KEYWORD); + verify(highlighting).highlight(142, 146, TypeOfText.KEYWORD); + verify(highlighting).highlight(162, 170, TypeOfText.COMMENT); + } + + @Test + public void testConvertHtmlToHighlightingWithMacEoL() throws Exception { + CodeColorizers codeColorizers = new CodeColorizers(Arrays.<CodeColorizerFormat>asList(new JavaScriptColorizerFormat(), new WebCodeColorizerFormat())); + File htmlFile = new File(this.getClass().getResource("CodeColorizersTest/package.html").toURI()); + SensorStorage sensorStorage = mock(SensorStorage.class); + DefaultHighlighting highlighting = new DefaultHighlighting(sensorStorage); + highlighting.onFile(new DefaultInputFile("FOO", "package.html") + .initMetadata(new FileMetadata().readMetadata(htmlFile, StandardCharsets.UTF_8))); + + codeColorizers.toSyntaxHighlighting(htmlFile, StandardCharsets.UTF_8, "web", highlighting); + + assertThat(highlighting.getSyntaxHighlightingRuleSet()).extracting("range.start.line", "range.start.lineOffset", "range.end.line", "range.end.lineOffset", "textType") + .containsExactly( + tuple(1, 0, 1, 132, TypeOfText.STRUCTURED_COMMENT), + tuple(2, 0, 2, 6, TypeOfText.KEYWORD), + tuple(3, 0, 3, 3, TypeOfText.KEYWORD), + tuple(4, 0, 4, 3, TypeOfText.KEYWORD), + // SONARWEB-26 + tuple(5, 42, 12, 0, TypeOfText.STRING)); + } + + public static class JavaScriptColorizerFormat extends CodeColorizerFormat { + + public JavaScriptColorizerFormat() { + super("js"); + } + + @Override + public List<Tokenizer> getTokenizers() { + return ImmutableList.<Tokenizer>of( + new StringTokenizer("<span class=\"s\">", "</span>"), + new CDocTokenizer("<span class=\"cd\">", "</span>"), + new JavadocTokenizer("<span class=\"cppd\">", "</span>"), + new CppDocTokenizer("<span class=\"cppd\">", "</span>"), + new KeywordsTokenizer("<span class=\"k\">", "</span>", "null", + "true", + "false", + "break", + "case", + "catch", + "class", + "continue", + "debugger", + "default", + "delete", + "do", + "extends", + "else", + "finally", + "for", + "function", + "if", + "import", + "in", + "instanceof", + "new", + "return", + "super", + "switch", + "this", + "throw", + "try", + "typeof", + "var", + "void", + "while", + "with", + "yield", + "const", + "enum", + "export")); + } + + } + + public class WebCodeColorizerFormat extends CodeColorizerFormat { + + private final List<Tokenizer> tokenizers = new ArrayList<>(); + + public WebCodeColorizerFormat() { + super("web"); + String tagAfter = "</span>"; + + // == tags == + tokenizers.add(new RegexpTokenizer("<span class=\"k\">", tagAfter, "</?[:\\w]+>?")); + tokenizers.add(new RegexpTokenizer("<span class=\"k\">", tagAfter, ">")); + + // == doctype == + tokenizers.add(new RegexpTokenizer("<span class=\"j\">", tagAfter, "<!DOCTYPE.*>")); + + // == comments == + tokenizers.add(new MultilinesDocTokenizer("<!--", "-->", "<span class=\"j\">", tagAfter)); + tokenizers.add(new MultilinesDocTokenizer("<%--", "--%>", "<span class=\"j\">", tagAfter)); + + // == expressions == + tokenizers.add(new MultilinesDocTokenizer("<%@", "%>", "<span class=\"a\">", tagAfter)); + tokenizers.add(new MultilinesDocTokenizer("<%", "%>", "<span class=\"a\">", tagAfter)); + + // == tag properties == + tokenizers.add(new StringTokenizer("<span class=\"s\">", tagAfter)); + } + + @Override + public List<Tokenizer> getTokenizers() { + return tokenizers; + } + + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/source/DefaultHighlightableTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/source/DefaultHighlightableTest.java new file mode 100644 index 00000000000..3a791783b1d --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/source/DefaultHighlightableTest.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.source; + +import java.io.StringReader; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.ArgumentCaptor; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.fs.internal.FileMetadata; +import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting; +import org.sonar.api.batch.sensor.internal.SensorStorage; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class DefaultHighlightableTest { + + @Rule + public ExpectedException throwable = ExpectedException.none(); + + @Test + public void should_store_highlighting_rules() { + SensorStorage sensorStorage = mock(SensorStorage.class); + DefaultInputFile inputFile = new DefaultInputFile("foo", "src/Foo.php") + .initMetadata(new FileMetadata().readMetadata(new StringReader("azerty\nbla bla"))); + DefaultHighlightable highlightablePerspective = new DefaultHighlightable(inputFile, sensorStorage, mock(AnalysisMode.class)); + highlightablePerspective.newHighlighting().highlight(0, 6, "k").highlight(7, 10, "cppd").done(); + + ArgumentCaptor<DefaultHighlighting> argCaptor = ArgumentCaptor.forClass(DefaultHighlighting.class); + verify(sensorStorage).store(argCaptor.capture()); + assertThat(argCaptor.getValue().getSyntaxHighlightingRuleSet()).hasSize(2); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/source/DefaultSymbolTableTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/source/DefaultSymbolTableTest.java new file mode 100644 index 00000000000..86c7b7b316d --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/source/DefaultSymbolTableTest.java @@ -0,0 +1,100 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.source; + +import org.sonar.api.batch.fs.TextRange; +import com.google.common.base.Strings; + +import java.io.StringReader; +import java.util.Set; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.fs.internal.FileMetadata; +import org.sonar.api.source.Symbol; +import org.sonar.api.source.Symbolizable; +import static org.assertj.core.api.Assertions.assertThat; + +public class DefaultSymbolTableTest { + + @Rule + public ExpectedException throwable = ExpectedException.none(); + private DefaultInputFile inputFile; + + @Before + public void prepare() { + inputFile = new DefaultInputFile("foo", "src/Foo.php") + .initMetadata(new FileMetadata().readMetadata(new StringReader(Strings.repeat("azerty\n", 20)))); + } + + @Test + public void should_order_symbol_and_references() { + + Symbolizable.SymbolTableBuilder symbolTableBuilder = new DefaultSymbolTable.Builder(inputFile); + Symbol firstSymbol = symbolTableBuilder.newSymbol(10, 20); + symbolTableBuilder.newReference(firstSymbol, 32); + Symbol secondSymbol = symbolTableBuilder.newSymbol(84, 92); + symbolTableBuilder.newReference(secondSymbol, 124); + Symbol thirdSymbol = symbolTableBuilder.newSymbol(55, 62); + symbolTableBuilder.newReference(thirdSymbol, 70); + Symbolizable.SymbolTable symbolTable = symbolTableBuilder.build(); + + assertThat(symbolTable.symbols()).containsExactly(firstSymbol, secondSymbol, thirdSymbol); + } + + @Test + public void variable_length_references() { + Symbolizable.SymbolTableBuilder symbolTableBuilder = new DefaultSymbolTable.Builder(inputFile); + Symbol firstSymbol = symbolTableBuilder.newSymbol(10, 20); + symbolTableBuilder.newReference(firstSymbol, 32); + symbolTableBuilder.newReference(firstSymbol, 44, 47); + + DefaultSymbolTable symbolTable = (DefaultSymbolTable) symbolTableBuilder.build(); + + assertThat(symbolTable.symbols()).containsExactly(firstSymbol); + + Set<TextRange> references = symbolTable.getReferencesBySymbol().get(firstSymbol); + assertThat(references).containsExactly(range(32, 42), range(44, 47)); + } + + private TextRange range(int start, int end) { + return inputFile.newRange(start, end); + } + + @Test + public void should_reject_reference_conflicting_with_declaration() { + throwable.expect(UnsupportedOperationException.class); + + Symbolizable.SymbolTableBuilder symbolTableBuilder = new DefaultSymbolTable.Builder(inputFile); + Symbol symbol = symbolTableBuilder.newSymbol(10, 20); + symbolTableBuilder.newReference(symbol, 15); + } + + @Test + public void test_toString() throws Exception { + Symbolizable.SymbolTableBuilder symbolTableBuilder = new DefaultSymbolTable.Builder(inputFile); + Symbol symbol = symbolTableBuilder.newSymbol(10, 20); + + assertThat(symbol.toString()).isEqualTo("Symbol{range=Range[from [line=2, lineOffset=3] to [line=3, lineOffset=6]]}"); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/source/DefaultSymbolizableTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/source/DefaultSymbolizableTest.java new file mode 100644 index 00000000000..f19a4b04099 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/source/DefaultSymbolizableTest.java @@ -0,0 +1,66 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.source; + +import com.google.common.base.Strings; +import java.io.StringReader; +import java.util.Map; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.fs.internal.FileMetadata; +import org.sonar.api.source.Symbol; +import org.sonar.api.source.Symbolizable; +import org.sonar.batch.sensor.DefaultSensorStorage; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class DefaultSymbolizableTest { + + @Test + public void should_update_cache_when_done() { + + DefaultSensorStorage sensorStorage = mock(DefaultSensorStorage.class); + DefaultInputFile inputFile = new DefaultInputFile("foo", "src/Foo.php") + .initMetadata(new FileMetadata().readMetadata(new StringReader(Strings.repeat("azerty\n", 20)))); + + DefaultSymbolizable symbolPerspective = new DefaultSymbolizable(inputFile, sensorStorage, mock(AnalysisMode.class)); + Symbolizable.SymbolTableBuilder symbolTableBuilder = symbolPerspective.newSymbolTableBuilder(); + Symbol firstSymbol = symbolTableBuilder.newSymbol(4, 8); + symbolTableBuilder.newReference(firstSymbol, 12); + symbolTableBuilder.newReference(firstSymbol, 70); + Symbol otherSymbol = symbolTableBuilder.newSymbol(25, 33); + symbolTableBuilder.newReference(otherSymbol, 44); + symbolTableBuilder.newReference(otherSymbol, 60); + symbolTableBuilder.newReference(otherSymbol, 108); + Symbolizable.SymbolTable symbolTable = symbolTableBuilder.build(); + + symbolPerspective.setSymbolTable(symbolTable); + + ArgumentCaptor<Map> argCaptor = ArgumentCaptor.forClass(Map.class); + verify(sensorStorage).store(eq(inputFile), argCaptor.capture()); + // Map<Symbol, Set<TextRange>> + assertThat(argCaptor.getValue().keySet()).hasSize(2); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/source/HighlightableBuilderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/source/HighlightableBuilderTest.java new file mode 100644 index 00000000000..07ca60048b3 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/source/HighlightableBuilderTest.java @@ -0,0 +1,58 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.source; + +import org.junit.Test; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.fs.internal.DefaultInputModule; +import org.sonar.api.batch.sensor.internal.SensorStorage; +import org.sonar.api.resources.File; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.api.source.Highlightable; +import org.sonar.batch.index.BatchComponent; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class HighlightableBuilderTest { + + @Test + public void should_load_default_perspective() { + Resource file = File.create("foo.c").setEffectiveKey("myproject:path/to/foo.c"); + BatchComponent component = new BatchComponent(1, file, null).setInputComponent(new DefaultInputFile("foo", "foo.c")); + + HighlightableBuilder builder = new HighlightableBuilder(mock(SensorStorage.class), mock(AnalysisMode.class)); + Highlightable perspective = builder.loadPerspective(Highlightable.class, component); + + assertThat(perspective).isNotNull().isInstanceOf(DefaultHighlightable.class); + } + + @Test + public void project_should_not_be_highlightable() { + BatchComponent component = new BatchComponent(1, new Project("struts").setEffectiveKey("org.struts"), null).setInputComponent(new DefaultInputModule("struts")); + + HighlightableBuilder builder = new HighlightableBuilder(mock(SensorStorage.class), mock(AnalysisMode.class)); + Highlightable perspective = builder.loadPerspective(Highlightable.class, component); + + assertThat(perspective).isNull(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/source/SymbolizableBuilderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/source/SymbolizableBuilderTest.java new file mode 100644 index 00000000000..5cc7fe2d06b --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/source/SymbolizableBuilderTest.java @@ -0,0 +1,59 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.source; + +import org.junit.Test; +import org.sonar.api.batch.AnalysisMode; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.fs.internal.DefaultInputModule; +import org.sonar.api.component.Perspective; +import org.sonar.api.resources.File; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.api.source.Symbolizable; +import org.sonar.batch.index.BatchComponent; +import org.sonar.batch.sensor.DefaultSensorStorage; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class SymbolizableBuilderTest { + + @Test + public void should_load_perspective() { + Resource file = File.create("foo.c").setEffectiveKey("myproject:path/to/foo.c"); + BatchComponent component = new BatchComponent(1, file, null).setInputComponent(new DefaultInputFile("foo", "foo.c")); + + SymbolizableBuilder perspectiveBuilder = new SymbolizableBuilder(mock(DefaultSensorStorage.class), mock(AnalysisMode.class)); + Perspective perspective = perspectiveBuilder.loadPerspective(Symbolizable.class, component); + + assertThat(perspective).isInstanceOf(Symbolizable.class); + } + + @Test + public void project_should_not_be_highlightable() { + BatchComponent component = new BatchComponent(1, new Project("struts").setEffectiveKey("org.struts"), null).setInputComponent(new DefaultInputModule("struts")); + + SymbolizableBuilder builder = new SymbolizableBuilder(mock(DefaultSensorStorage.class), mock(AnalysisMode.class)); + Perspective perspective = builder.loadPerspective(Symbolizable.class, component); + + assertThat(perspective).isNull(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/task/ListTaskTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/task/ListTaskTest.java new file mode 100644 index 00000000000..0bf7ef0afc2 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/task/ListTaskTest.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.task; + +import java.util.Arrays; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.task.Task; +import org.sonar.api.task.TaskDefinition; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ListTaskTest { + + @Rule + public LogTester logTester = new LogTester(); + + @Test + public void should_list_available_tasks() { + Tasks tasks = mock(Tasks.class); + when(tasks.definitions()).thenReturn(Arrays.asList( + TaskDefinition.builder().key("foo").description("Foo").taskClass(FooTask.class).build(), + TaskDefinition.builder().key("purge").description("Purge database").taskClass(FakePurgeTask.class).build())); + + ListTask task = new ListTask(tasks); + + task.execute(); + + assertThat(logTester.logs(LoggerLevel.INFO)).hasSize(1); + assertThat(logTester.logs(LoggerLevel.INFO).get(0)).contains("Available tasks:", " - foo: Foo", " - purge: Purge database"); + } + + private static class FakePurgeTask implements Task { + public void execute() { + } + } + + private static class FooTask implements Task { + public void execute() { + } + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/task/TasksTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/task/TasksTest.java new file mode 100644 index 00000000000..b2d52b253f2 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/task/TasksTest.java @@ -0,0 +1,90 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.task; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.task.Task; +import org.sonar.api.task.TaskDefinition; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TasksTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void should_get_definitions() { + Tasks tasks = new Tasks(new TaskDefinition[] {ScanTask.DEFINITION, ListTask.DEFINITION}); + assertThat(tasks.definitions()).hasSize(2); + } + + @Test + public void should_get_definition_by_key() { + Tasks tasks = new Tasks(new TaskDefinition[] {ScanTask.DEFINITION, ListTask.DEFINITION}); + tasks.start(); + assertThat(tasks.definition(ListTask.DEFINITION.key())).isEqualTo(ListTask.DEFINITION); + } + + @Test + public void should_return_null_if_task_not_found() { + Tasks tasks = new Tasks(new TaskDefinition[] {ScanTask.DEFINITION, ListTask.DEFINITION}); + + assertThat(tasks.definition("not-exists")).isNull(); + } + + @Test + public void should_fail_on_duplicated_keys() { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Task 'foo' is declared twice"); + + new Tasks(new TaskDefinition[] { + TaskDefinition.builder().key("foo").taskClass(FakeTask1.class).description("foo1").build(), + TaskDefinition.builder().key("foo").taskClass(FakeTask2.class).description("foo2").build() + }); + } + + @Test + public void should_fail_on_duplicated_class() { + Tasks tasks = new Tasks(new TaskDefinition[] { + TaskDefinition.builder().key("foo1").taskClass(FakeTask1.class).description("foo1").build(), + TaskDefinition.builder().key("foo2").taskClass(FakeTask1.class).description("foo1").build() + }); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Task 'org.sonar.batch.task.TasksTest$FakeTask1' is defined twice: first by 'foo1' and then by 'foo2'"); + + tasks.start(); + } + + private static class FakeTask1 implements Task { + public void execute() { + } + } + + private static class FakeTask2 implements Task { + public void execute() { + } + + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/util/BatchUtilsTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/util/BatchUtilsTest.java new file mode 100644 index 00000000000..8de6036b5e3 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/util/BatchUtilsTest.java @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.util; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BatchUtilsTest { + + @Test + public void encodeForUrl() throws Exception { + assertThat(BatchUtils.encodeForUrl(null)).isEqualTo(""); + assertThat(BatchUtils.encodeForUrl("")).isEqualTo(""); + assertThat(BatchUtils.encodeForUrl("foo")).isEqualTo("foo"); + assertThat(BatchUtils.encodeForUrl("foo&bar")).isEqualTo("foo%26bar"); + } + + @Test + + public void testDescribe() { + Object withToString = new Object() { + @Override + public String toString() { + return "desc"; + } + }; + + Object withoutToString = new Object(); + + assertThat(BatchUtils.describe(withToString)).isEqualTo(("desc")); + assertThat(BatchUtils.describe(withoutToString)).isEqualTo("java.lang.Object"); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/util/ProgressReportTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/util/ProgressReportTest.java new file mode 100644 index 00000000000..b21ad736fde --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/util/ProgressReportTest.java @@ -0,0 +1,90 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.util; + +import java.util.Set; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.log.LogTester; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ProgressReportTest { + private static final String THREAD_NAME = "progress"; + private ProgressReport progressReport; + + @Rule + public LogTester logTester = new LogTester(); + + @Before + public void setUp() { + progressReport = new ProgressReport(THREAD_NAME, 100); + } + + @Test + public void die_on_stop() { + progressReport.start("start"); + assertThat(isThreadAlive(THREAD_NAME)).isTrue(); + progressReport.stop("stop"); + assertThat(isThreadAlive(THREAD_NAME)).isFalse(); + } + + @Test + public void do_not_block_app() { + progressReport.start("start"); + assertThat(isDaemon(THREAD_NAME)).isTrue(); + progressReport.stop("stop"); + } + + @Test + public void do_log() { + progressReport.start("start"); + progressReport.message("Some message"); + try { + Thread.sleep(200); + } catch (InterruptedException e) { + // Ignore + } + progressReport.stop("stop"); + assertThat(logTester.logs()).contains("Some message"); + } + + private static boolean isDaemon(String name) { + Thread t = getThread(name); + return (t != null) && t.isDaemon(); + } + + private static boolean isThreadAlive(String name) { + Thread t = getThread(name); + return (t != null) && t.isAlive(); + } + + private static Thread getThread(String name) { + Set<Thread> threads = Thread.getAllStackTraces().keySet(); + + for (Thread t : threads) { + if (t.getName().equals(name)) { + return t; + } + } + return null; + } +} diff --git a/sonar-scanner-engine/src/test/resources/logback-test.xml b/sonar-scanner-engine/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..da6be3344a2 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/logback-test.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<configuration debug="false"> + + <!-- + ONLY FOR UNIT TESTS + --> + + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <pattern>%d{HH:mm:ss.SSS} %-5level - %msg%n</pattern> + </encoder> + </appender> + + <!-- BeanUtils generate to many DEBUG logs when sonar.verbose is set --> + <logger name="org.apache.commons.beanutils.converters"> + <level value="WARN"/> + </logger> + + <!-- sonar.showSql --> + <!-- see also org.sonar.db.MyBatis#configureLogback() --> + <logger name="org.mybatis"> + <level value="WARN"/> + </logger> + <logger name="org.apache.ibatis"> + <level value="WARN"/> + </logger> + <logger name="java.sql"> + <level value="WARN"/> + </logger> + <logger name="java.sql.ResultSet"> + <level value="WARN"/> + </logger> + <logger name="PERSISTIT"> + <level value="WARN"/> + </logger> + + <root> + <level value="INFO"/> + <appender-ref ref="STDOUT"/> + </root> + +</configuration> diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample-not-associated/module_a/module_a1/src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample-not-associated/module_a/module_a1/src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo new file mode 100644 index 00000000000..74d29a4fa08 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample-not-associated/module_a/module_a1/src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo @@ -0,0 +1,16 @@ +package com.sonar.it.samples.modules.a1; + +public class HelloA1 { + private int i; + private HelloA1() { + + } + + public void hello() { + System.out.println("hello" + " xoo"); + } + + protected String getHello() { + return "hello"; + } +}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample-not-associated/module_a/module_a2/src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample-not-associated/module_a/module_a2/src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo new file mode 100644 index 00000000000..42039538a92 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample-not-associated/module_a/module_a2/src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo @@ -0,0 +1,12 @@ +package com.sonar.it.samples.modules.a2; + +public class HelloA2 { + private int i; + private HelloA2() { + + } + + public void hello() { + System.out.println("hello" + " xoo"); + } +}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample-not-associated/module_b/module_b1/src/main/xoo/com/sonar/it/samples/modules/b1/HelloB1.xoo b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample-not-associated/module_b/module_b1/src/main/xoo/com/sonar/it/samples/modules/b1/HelloB1.xoo new file mode 100644 index 00000000000..b83c3af128c --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample-not-associated/module_b/module_b1/src/main/xoo/com/sonar/it/samples/modules/b1/HelloB1.xoo @@ -0,0 +1,12 @@ +package com.sonar.it.samples.modules.b1; + +public class HelloB1 { + private int i; + private HelloB1() { + + } + + public void hello() { + System.out.println("hello" + " world"); + } +}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample-not-associated/module_b/module_b2/src/main/xoo/com/sonar/it/samples/modules/b2/HelloB2.xoo b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample-not-associated/module_b/module_b2/src/main/xoo/com/sonar/it/samples/modules/b2/HelloB2.xoo new file mode 100644 index 00000000000..20b8bb3876a --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample-not-associated/module_b/module_b2/src/main/xoo/com/sonar/it/samples/modules/b2/HelloB2.xoo @@ -0,0 +1,12 @@ +package com.sonar.it.samples.modules.b2; + +public class HelloB2 { + private int i; + private HelloB2() { + + } + + public void hello() { + System.out.println("hello" + " world"); + } +}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample-not-associated/sonar-project.properties b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample-not-associated/sonar-project.properties new file mode 100644 index 00000000000..c2b00ede37c --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample-not-associated/sonar-project.properties @@ -0,0 +1,31 @@ +# Root project information +#sonar.projectKey=com.sonarsource.it.samples:multi-modules-sample +sonar.projectName=Sonar :: Integration Tests :: Multi-modules Sample +sonar.projectVersion=1.0-SNAPSHOT + +sonar.language=xoo + +# Some properties that will be inherited by the modules +sonar.sources=src/main/xoo + +# List of the module identifiers +sonar.modules=module_a,module_b + +module_a.sonar.projectKey=module_a +module_a.sonar.projectName=Module A + +module_a.sonar.modules=module_a1,module_a2 + +module_a.module_a1.sonar.projectName=Sub-module A1 + +module_a.module_a2.sonar.projectName=Sub-module A2 + + +module_b.sonar.projectKey=module_b +module_b.sonar.projectName=Module B + +module_b.sonar.modules=module_b1,module_b2 + +module_b.module_b1.sonar.projectName=Sub-module B1 + +module_b.module_b2.sonar.projectName=Sub-module B2 diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample/module_a/module_a1/.sonar/sonar-report.json b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample/module_a/module_a1/.sonar/sonar-report.json new file mode 100644 index 00000000000..581142ee53c --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample/module_a/module_a1/.sonar/sonar-report.json @@ -0,0 +1 @@ +{"version":"5.1-SNAPSHOT","issues":[{"key":"0ae8428f-42a6-4b44-befc-512f1846fdad","component":"com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo","line":1,"message":"This issue is generated on each line","severity":"MAJOR","rule":"xoo:OneIssuePerLine","status":"OPEN","isNew":false,"creationDate":"2013-05-01T00:00:00+0200"},{"key":"215429f4-fa2e-4611-a75b-abb4330ea36b","component":"com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo","line":4,"message":"This issue is generated on each line","severity":"MAJOR","rule":"xoo:OneIssuePerLine","status":"OPEN","isNew":false,"creationDate":"2013-05-01T00:00:00+0200"},{"key":"2b5d88b5-acc5-4761-bf47-3d6d46435c59","component":"com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo","line":10,"message":"This issue is generated on each line","severity":"MAJOR","rule":"xoo:OneIssuePerLine","status":"OPEN","isNew":false,"creationDate":"2013-05-01T00:00:00+0200"},{"key":"50fb32b3-61af-4efa-b234-5a5b048d72a1","component":"com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo","line":16,"message":"This issue is generated on each line","severity":"MAJOR","rule":"xoo:OneIssuePerLine","status":"OPEN","isNew":false,"creationDate":"2013-05-01T00:00:00+0200"},{"key":"52911422-9c22-4a40-982f-c7fa779e3b2c","component":"com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo","line":2,"message":"This issue is generated on each line","severity":"MAJOR","rule":"xoo:OneIssuePerLine","status":"OPEN","isNew":false,"creationDate":"2013-05-01T00:00:00+0200"},{"key":"5b0da657-2183-42ae-b0f9-595a0f59de17","component":"com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo","line":15,"message":"This issue is generated on each line","severity":"MAJOR","rule":"xoo:OneIssuePerLine","status":"OPEN","isNew":false,"creationDate":"2013-05-01T00:00:00+0200"},{"key":"70bac047-8559-4c72-bc2d-7584df7e4a0b","component":"com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo","line":12,"message":"This issue is generated on each line","severity":"MAJOR","rule":"xoo:OneIssuePerLine","status":"OPEN","isNew":false,"creationDate":"2013-05-01T00:00:00+0200"},{"key":"8843a921-b9bd-4837-ae0b-423d01b2476d","component":"com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo","line":14,"message":"This issue is generated on each line","severity":"MAJOR","rule":"xoo:OneIssuePerLine","status":"OPEN","isNew":false,"creationDate":"2013-05-01T00:00:00+0200"},{"key":"b227df4a-78e1-4624-9f48-589000d26fe9","component":"com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo","line":8,"message":"This issue is generated on each line","severity":"MAJOR","rule":"xoo:OneIssuePerLine","status":"OPEN","isNew":false,"creationDate":"2013-05-01T00:00:00+0200"},{"key":"b6f8a23f-de5a-4ec2-a549-ace73f0e0667","component":"com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo","line":9,"message":"This issue is generated on each line","severity":"MAJOR","rule":"xoo:OneIssuePerLine","status":"OPEN","isNew":false,"creationDate":"2013-05-01T00:00:00+0200"},{"key":"c7aef709-5210-4ff2-a237-be915ad611bb","component":"com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo","line":13,"message":"This issue is generated on each line","severity":"MAJOR","rule":"xoo:OneIssuePerLine","status":"OPEN","isNew":false,"creationDate":"2013-05-01T00:00:00+0200"},{"key":"c86663e2-6b98-4e9f-80c3-3cdea013f816","component":"com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo","line":11,"message":"This issue is generated on each line","severity":"MAJOR","rule":"xoo:OneIssuePerLine","status":"OPEN","isNew":false,"creationDate":"2013-05-01T00:00:00+0200"},{"key":"cddb488f-37ba-42a6-9f2a-09090c2f1c8e","component":"com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo","line":3,"message":"This issue is generated on each line","severity":"MAJOR","rule":"xoo:OneIssuePerLine","status":"OPEN","isNew":false,"creationDate":"2013-05-01T00:00:00+0200"},{"key":"e1d513d2-b165-41a5-9874-b0dc01fef619","component":"com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo","line":6,"message":"This issue is generated on each line","severity":"MAJOR","rule":"xoo:OneIssuePerLine","status":"OPEN","isNew":false,"creationDate":"2013-05-01T00:00:00+0200"},{"key":"e49d6d6e-3e2d-425b-b531-fe761757b773","component":"com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo","line":5,"message":"This issue is generated on each line","severity":"MAJOR","rule":"xoo:OneIssuePerLine","status":"OPEN","isNew":false,"creationDate":"2013-05-01T00:00:00+0200"},{"key":"f2ba9cdb-7efe-413f-981c-e6421861db46","component":"com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo","line":7,"message":"This issue is generated on each line","severity":"MAJOR","rule":"xoo:OneIssuePerLine","status":"OPEN","isNew":false,"creationDate":"2013-05-01T00:00:00+0200"}],"components":[{"key":"com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1"},{"key":"com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo","path":"src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo","moduleKey":"com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1","status":"SAME"},{"key":"com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1","path":"src/main/xoo/com/sonar/it/samples/modules/a1","moduleKey":"com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1"}],"rules":[{"key":"xoo:OneIssuePerLine","rule":"OneIssuePerLine","repository":"xoo","name":"One Issue Per Line"}],"users":[]}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample/module_a/module_a1/src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample/module_a/module_a1/src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo new file mode 100644 index 00000000000..74d29a4fa08 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample/module_a/module_a1/src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo @@ -0,0 +1,16 @@ +package com.sonar.it.samples.modules.a1; + +public class HelloA1 { + private int i; + private HelloA1() { + + } + + public void hello() { + System.out.println("hello" + " xoo"); + } + + protected String getHello() { + return "hello"; + } +}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample/module_a/module_a2/src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample/module_a/module_a2/src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo new file mode 100644 index 00000000000..42039538a92 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample/module_a/module_a2/src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo @@ -0,0 +1,12 @@ +package com.sonar.it.samples.modules.a2; + +public class HelloA2 { + private int i; + private HelloA2() { + + } + + public void hello() { + System.out.println("hello" + " xoo"); + } +}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample/module_b/module_b1/src/main/xoo/com/sonar/it/samples/modules/b1/HelloB1.xoo b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample/module_b/module_b1/src/main/xoo/com/sonar/it/samples/modules/b1/HelloB1.xoo new file mode 100644 index 00000000000..b83c3af128c --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample/module_b/module_b1/src/main/xoo/com/sonar/it/samples/modules/b1/HelloB1.xoo @@ -0,0 +1,12 @@ +package com.sonar.it.samples.modules.b1; + +public class HelloB1 { + private int i; + private HelloB1() { + + } + + public void hello() { + System.out.println("hello" + " world"); + } +}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample/module_b/module_b2/src/main/xoo/com/sonar/it/samples/modules/b2/HelloB2.xoo b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample/module_b/module_b2/src/main/xoo/com/sonar/it/samples/modules/b2/HelloB2.xoo new file mode 100644 index 00000000000..20b8bb3876a --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample/module_b/module_b2/src/main/xoo/com/sonar/it/samples/modules/b2/HelloB2.xoo @@ -0,0 +1,12 @@ +package com.sonar.it.samples.modules.b2; + +public class HelloB2 { + private int i; + private HelloB2() { + + } + + public void hello() { + System.out.println("hello" + " world"); + } +}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample/sonar-project.properties b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample/sonar-project.properties new file mode 100644 index 00000000000..b07be6f3e6f --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/multi-modules-sample/sonar-project.properties @@ -0,0 +1,31 @@ +# Root project information +sonar.projectKey=com.sonarsource.it.samples:multi-modules-sample +sonar.projectName=Sonar :: Integration Tests :: Multi-modules Sample +sonar.projectVersion=1.0-SNAPSHOT + +sonar.language=xoo + +# Some properties that will be inherited by the modules +sonar.sources=src/main/xoo + +# List of the module identifiers +sonar.modules=module_a,module_b + +module_a.sonar.projectKey=module_a +module_a.sonar.projectName=Module A + +module_a.sonar.modules=module_a1,module_a2 + +module_a.module_a1.sonar.projectName=Sub-module A1 + +module_a.module_a2.sonar.projectName=Sub-module A2 + + +module_b.sonar.projectKey=module_b +module_b.sonar.projectName=Module B + +module_b.sonar.modules=module_b1,module_b2 + +module_b.module_b1.sonar.projectName=Sub-module B1 + +module_b.module_b2.sonar.projectName=Sub-module B2 diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-multiline/sonar-project.properties b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-multiline/sonar-project.properties new file mode 100644 index 00000000000..0c8e5dc5354 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-multiline/sonar-project.properties @@ -0,0 +1,4 @@ +sonar.projectKey=sample-multiline +sonar.projectName=Sample Multiline +sonar.projectVersion=0.1-SNAPSHOT +sonar.sources=xources diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-multiline/xources/hello/Multiline.xoo b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-multiline/xources/hello/Multiline.xoo new file mode 100644 index 00000000000..6e8a35f20a5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-multiline/xources/hello/Multiline.xoo @@ -0,0 +1,9 @@ +package hello; + +public class HelloJava { + + public static void main(String[] args) { + {xoo-start-issue:1}System.out + .println("Hello"){xoo-end-issue:1}; + } +}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-multiline/xources/hello/Multiple.xoo b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-multiline/xources/hello/Multiple.xoo new file mode 100644 index 00000000000..b6b1b8369a4 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-multiline/xources/hello/Multiple.xoo @@ -0,0 +1,9 @@ +package hello; + +public class HelloJava { + + public static void main(String[] args) { + {xoo-start-issue:1}System.out.println("Hello"){xoo-end-issue:1}; + {xoo-start-flow:1:1:1}System.out.println("World"){xoo-end-flow:1:1:1}; + } +}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-multiline/xources/hello/Single.xoo b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-multiline/xources/hello/Single.xoo new file mode 100644 index 00000000000..fc664425a99 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-multiline/xources/hello/Single.xoo @@ -0,0 +1,8 @@ +package hello; + +public class HelloJava { + + public static void main(String[] args) { + {xoo-start-issue:1}System.out.println("Hello"){xoo-end-issue:1}; + } +}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-multiline/xources/hello/WithFlow.xoo b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-multiline/xources/hello/WithFlow.xoo new file mode 100644 index 00000000000..9dc4685fe84 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-multiline/xources/hello/WithFlow.xoo @@ -0,0 +1,14 @@ +package hello; + +public class HelloJava { + + public static void main(String[] args) { + {xoo-start-flow:1:1:1}if (true){xoo-end-flow:1:1:1} { + {xoo-start-flow:1:1:2}if (true){xoo-end-flow:1:1:2} { + {xoo-start-issue:1}if (true){xoo-end-issue:1} { + System.out.println("Hello"); + } + } + } + } +}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-with-empty-file/sonar-project.properties b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-with-empty-file/sonar-project.properties new file mode 100644 index 00000000000..58f27e81f61 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-with-empty-file/sonar-project.properties @@ -0,0 +1,5 @@ +sonar.projectKey=sample-with-empty-file +sonar.projectName=Sample With Empty +sonar.projectVersion=0.1-SNAPSHOT +sonar.sources=xources +sonar.language=xoo diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-with-empty-file/xources/hello/Empty.xoo b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-with-empty-file/xources/hello/Empty.xoo new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-with-empty-file/xources/hello/Empty.xoo diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-with-empty-file/xources/hello/HelloJava.xoo b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-with-empty-file/xources/hello/HelloJava.xoo new file mode 100644 index 00000000000..1d9c60d56b7 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-with-empty-file/xources/hello/HelloJava.xoo @@ -0,0 +1,8 @@ +package hello; + +public class HelloJava { + + public static void main(String[] args) { + System.out.println("Hello"); + } +}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-with-symlink/.gitignore b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-with-symlink/.gitignore new file mode 100644 index 00000000000..ecbefd4f19d --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-with-symlink/.gitignore @@ -0,0 +1 @@ +.sonar diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-with-symlink/sonar-project.properties b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-with-symlink/sonar-project.properties new file mode 100644 index 00000000000..8810e376701 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-with-symlink/sonar-project.properties @@ -0,0 +1,6 @@ +sonar.projectKey=sample +sonar.projectName=Sample +sonar.projectVersion=0.1-SNAPSHOT +sonar.sources=xources +sonar.tests=testx +sonar.language=xoo diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-with-symlink/testx b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-with-symlink/testx new file mode 120000 index 00000000000..7385ebd51cf --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-with-symlink/testx @@ -0,0 +1 @@ +../sample/testx/
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-with-symlink/xources b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-with-symlink/xources new file mode 120000 index 00000000000..15dca9d90d2 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample-with-symlink/xources @@ -0,0 +1 @@ +../sample/xources/
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/sonar-project.properties b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/sonar-project.properties new file mode 100644 index 00000000000..8810e376701 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/sonar-project.properties @@ -0,0 +1,6 @@ +sonar.projectKey=sample +sonar.projectName=Sample +sonar.projectVersion=0.1-SNAPSHOT +sonar.sources=xources +sonar.tests=testx +sonar.language=xoo diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/testx/ClassOneTest.xoo b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/testx/ClassOneTest.xoo new file mode 100644 index 00000000000..8c0967e496f --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/testx/ClassOneTest.xoo @@ -0,0 +1,11 @@ +package org.sonar.tests; + +import org.junit.Test; + +public class ClassOneTest { + + @Test + public void nothing() { + + } +} diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/testx/ClassOneTest.xoo.measures b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/testx/ClassOneTest.xoo.measures new file mode 100644 index 00000000000..23b08dc0e0e --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/testx/ClassOneTest.xoo.measures @@ -0,0 +1,7 @@ +lines:11 +ncloc:7 +tests:1 +test_execution_time:1 +skipped_tests:0 +test_errors:0 +test_failures:0 diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/testx/ClassOneTest.xoo.scm b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/testx/ClassOneTest.xoo.scm new file mode 100644 index 00000000000..2cec35b8a72 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/testx/ClassOneTest.xoo.scm @@ -0,0 +1,11 @@ +1,user1,2013-01-04 +1,user1,2013-01-04 +1,user1,2013-01-04 +1,user1,2013-01-04 +2,user2,2013-01-05 +2,user2,2013-01-05 +3,user3,2013-01-06 +4,user4,2013-01-07 +4,user4,2013-01-07 +4,user4,2013-01-07 +4,user4,2013-01-07
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/xources/hello/HelloJava.xoo b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/xources/hello/HelloJava.xoo new file mode 100644 index 00000000000..1d9c60d56b7 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/xources/hello/HelloJava.xoo @@ -0,0 +1,8 @@ +package hello; + +public class HelloJava { + + public static void main(String[] args) { + System.out.println("Hello"); + } +}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/xources/hello/HelloJava.xoo.measures b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/xources/hello/HelloJava.xoo.measures new file mode 100644 index 00000000000..9eaf8ba2549 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/xources/hello/HelloJava.xoo.measures @@ -0,0 +1,2 @@ +ncloc:3 +complexity:1 diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/xources/hello/HelloJava.xoo.scm b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/xources/hello/HelloJava.xoo.scm new file mode 100644 index 00000000000..03a9de2f486 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/xources/hello/HelloJava.xoo.scm @@ -0,0 +1,8 @@ +1,user1,2013-01-04 +1,user1,2013-01-04 +1,user1,2013-01-04 +1,user1,2013-01-04 +2,user2,2013-01-05 +2,user2,2013-01-05 +3,user3,2013-01-06 +4,user4,2013-01-07
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/xources/hello/helloscala.xoo b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/xources/hello/helloscala.xoo new file mode 100644 index 00000000000..53cb085156c --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/xources/hello/helloscala.xoo @@ -0,0 +1,6 @@ + object HelloWorld { + def main(args: Array[String]) { + println("Hello, world of xoo!") + } + } +
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/xources/hello/helloscala.xoo.measures b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/xources/hello/helloscala.xoo.measures new file mode 100644 index 00000000000..d2c8386aed1 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/mediumtest/xoo/sample/xources/hello/helloscala.xoo.measures @@ -0,0 +1,2 @@ +ncloc:5 +complexity:2 diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/bootstrap/BatchPluginJarExploderTest/sonar-checkstyle-plugin-2.8.jar b/sonar-scanner-engine/src/test/resources/org/sonar/batch/bootstrap/BatchPluginJarExploderTest/sonar-checkstyle-plugin-2.8.jar Binary files differnew file mode 100644 index 00000000000..f937399bec5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/bootstrap/BatchPluginJarExploderTest/sonar-checkstyle-plugin-2.8.jar diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/api_rules_list.protobuf b/sonar-scanner-engine/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/api_rules_list.protobuf Binary files differnew file mode 100644 index 00000000000..1d417ce2880 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/api_rules_list.protobuf diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/batch_issues.protobuf b/sonar-scanner-engine/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/batch_issues.protobuf Binary files differnew file mode 100644 index 00000000000..8b610d8f73c --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/batch_issues.protobuf diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/batch_project.json b/sonar-scanner-engine/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/batch_project.json new file mode 100644 index 00000000000..2887ce18d10 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/batch_project.json @@ -0,0 +1,164 @@ +{ + "timestamp": 0, + "qprofilesByLanguage": { + "java": { + "key": "java-sonar-way-72608", + "name": "Sonar way", + "language": "java", + "rulesUpdatedAt": "2015-08-10T12:06:53+0200" + } + }, + "activeRules": [ + { + "repositoryKey": "common-java", + "ruleKey": "DuplicatedBlocks", + "name": "Source files should not have any duplicated blocks", + "severity": "MAJOR", + "language": "java", + "params": {} + }, + { + "repositoryKey": "common-java", + "ruleKey": "InsufficientBranchCoverage", + "name": "Branches should have sufficient coverage by unit tests", + "severity": "MAJOR", + "language": "java", + "params": { + "minimumBranchCoverageRatio": "65.0" + } + }, + { + "repositoryKey": "squid", + "ruleKey": "RightCurlyBraceStartLineCheck", + "name": "A close curly brace should be located at the beginning of a line", + "severity": "MINOR", + "internalKey": "RightCurlyBraceStartLineCheck", + "language": "java", + "params": {} + }, + { + "repositoryKey": "squid", + "ruleKey": "UselessParenthesesCheck", + "name": "Useless parentheses around expressions should be removed to prevent any misunderstanding", + "severity": "MAJOR", + "internalKey": "UselessParenthesesCheck", + "language": "java", + "params": {} + }, + { + "repositoryKey": "squid", + "ruleKey": "ObjectFinalizeCheck", + "name": "The Object.finalize() method should not be called", + "severity": "CRITICAL", + "internalKey": "ObjectFinalizeCheck", + "language": "java", + "params": {} + }, + { + "repositoryKey": "squid", + "ruleKey": "ObjectFinalizeOverridenCheck", + "name": "The Object.finalize() method should not be overriden", + "severity": "CRITICAL", + "internalKey": "ObjectFinalizeOverridenCheck", + "language": "java", + "params": {} + }, + { + "repositoryKey": "squid", + "ruleKey": "ObjectFinalizeOverridenCallsSuperFinalizeCheck", + "name": "super.finalize() should be called at the end of Object.finalize() implementations", + "severity": "BLOCKER", + "internalKey": "ObjectFinalizeOverridenCallsSuperFinalizeCheck", + "language": "java", + "params": {} + }, + { + "repositoryKey": "squid", + "ruleKey": "ClassVariableVisibilityCheck", + "name": "Class variable fields should not have public accessibility", + "severity": "MAJOR", + "internalKey": "ClassVariableVisibilityCheck", + "language": "java", + "params": {} + }, + { + "repositoryKey": "squid", + "ruleKey": "S2188", + "name": "JUnit test cases should call super methods", + "severity": "CRITICAL", + "internalKey": "S2188", + "language": "java", + "params": {} + }, + { + "repositoryKey": "squid", + "ruleKey": "S2186", + "name": "JUnit assertions should not be used in \"run\" methods", + "severity": "CRITICAL", + "internalKey": "S2186", + "language": "java", + "params": {} + }, + { + "repositoryKey": "squid", + "ruleKey": "S2187", + "name": "TestCases should contain tests", + "severity": "MAJOR", + "internalKey": "S2187", + "language": "java", + "params": {} + }, + { + "repositoryKey": "squid", + "ruleKey": "S2391", + "name": "JUnit framework methods should be declared properly", + "severity": "CRITICAL", + "internalKey": "S2391", + "language": "java", + "params": {} + }, + { + "repositoryKey": "squid", + "ruleKey": "S2325", + "name": "\"private\" methods that don\u0027t access instance data should be \"static\"", + "severity": "MINOR", + "internalKey": "S2325", + "language": "java", + "params": {} + }, + { + "repositoryKey": "squid", + "ruleKey": "S1166", + "name": "Exception handlers should preserve the original exception", + "severity": "CRITICAL", + "internalKey": "S1166", + "language": "java", + "params": { + "exceptions": "java.lang.InterruptedException, java.lang.NumberFormatException, java.text.ParseException, java.net.MalformedURLException" + } + }, + { + "repositoryKey": "squid", + "ruleKey": "S2970", + "name": "Assertions should be complete", + "severity": "CRITICAL", + "internalKey": "S2970", + "language": "java", + "params": {} + } + + ], + "settingsByModule": {}, + "fileDataByModuleAndPath": { + "org.codehaus.sonar-plugins:sonar-scm-git-plugin": { + "src/test/java/org/sonar/plugins/scm/git/JGitBlameCommandTest.java": { + "needBlame": true + }, + "src/main/java/org/sonar/plugins/scm/git/GitScmProvider.java": { + "hash": "90082117d0dc0f1189ab7e4990a20667", + "needBlame": true + } + } + }, + "lastAnalysisDate": "2015-08-10T13:20:09+0200" +}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/batch_users.protobuf b/sonar-scanner-engine/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/batch_users.protobuf new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/cache/ProjectCacheSynchronizerTest/batch_users.protobuf diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/cpd/ManyStatements.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/cpd/ManyStatements.java new file mode 100644 index 00000000000..ed2297068e4 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/cpd/ManyStatements.java @@ -0,0 +1,11 @@ +package org.foo; + +public class ManyStatements { + + void foo() { + int A1 = 0; int B = 0; int C = 0; int D = 0; int E = 0; int F = 0; int G = 0; int H = 0; int I = 0; int J = 0; int K = 0; + int A2 = 0; int B = 0; int C = 0; int D = 0; int E = 0; int F = 0; int G = 0; int H = 0; int I = 0; int J = 0; int K = 0; + int A1 = 0; int B = 0; int C = 0; int D = 0; int E = 0; int F = 0; int G = 0; int H = 0; int I = 0; int J = 0; int K = 0; + } + +}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-double-regexp-mess.txt b/sonar-scanner-engine/src/test/resources/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-double-regexp-mess.txt new file mode 100644 index 00000000000..48d30c92f97 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-double-regexp-mess.txt @@ -0,0 +1,37 @@ +package org.sonar.plugins.switchoffviolations.pattern; + +import com.google.common.collect.Sets; + +import java.util.Set; + +/** + * + */ +public class LineRange { + int from, to; + + public LineRange(int from, int to) { + if (to < from) { + throw new IllegalArgumentException("Line range is not valid: " + from + " must be greater than " + to); + } + this.from = from; + this.to = to; + } + + // SONAR-OFF + public boolean in(int lineId) { + return from <= lineId && lineId <= to; + } + // FOO-OFF + + public Set<Integer> toLines() { + Set<Integer> lines = Sets.newLinkedHashSet(); + // SONAR-ON + for (int index = from; index <= to; index++) { + lines.add(index); + } + // FOO-ON + return lines; + } + +}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-double-regexp-twice.txt b/sonar-scanner-engine/src/test/resources/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-double-regexp-twice.txt new file mode 100644 index 00000000000..9ae63dc57f9 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-double-regexp-twice.txt @@ -0,0 +1,37 @@ +package org.sonar.plugins.switchoffviolations.pattern; + +import com.google.common.collect.Sets; + +import java.util.Set; + +/** + * + */ +public class LineRange { + int from, to; + + public LineRange(int from, int to) { + if (to < from) { + throw new IllegalArgumentException("Line range is not valid: " + from + " must be greater than " + to); + } + this.from = from; + this.to = to; + } + + // SONAR-OFF + public boolean in(int lineId) { + return from <= lineId && lineId <= to; + } + // SONAR-ON + + public Set<Integer> toLines() { + Set<Integer> lines = Sets.newLinkedHashSet(); + // FOO-OFF + for (int index = from; index <= to; index++) { + lines.add(index); + } + // FOO-ON + return lines; + } + +}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-double-regexp-unfinished.txt b/sonar-scanner-engine/src/test/resources/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-double-regexp-unfinished.txt new file mode 100644 index 00000000000..dd7656180ab --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-double-regexp-unfinished.txt @@ -0,0 +1,34 @@ +package org.sonar.plugins.switchoffviolations.pattern; + +import com.google.common.collect.Sets; + +import java.util.Set; + +/** + * + */ +public class LineRange { + int from, to; + + public LineRange(int from, int to) { + if (to < from) { + throw new IllegalArgumentException("Line range is not valid: " + from + " must be greater than " + to); + } + this.from = from; + this.to = to; + } + + // SONAR-OFF + public boolean in(int lineId) { + return from <= lineId && lineId <= to; + } + + public Set<Integer> toLines() { + Set<Integer> lines = Sets.newLinkedHashSet(); + for (int index = from; index <= to; index++) { + lines.add(index); + } + return lines; + } + +}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-double-regexp-wrong-order.txt b/sonar-scanner-engine/src/test/resources/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-double-regexp-wrong-order.txt new file mode 100644 index 00000000000..7cac0b98aed --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-double-regexp-wrong-order.txt @@ -0,0 +1,35 @@ +package org.sonar.plugins.switchoffviolations.pattern; + +import com.google.common.collect.Sets; + +import java.util.Set; + +/** + * + */ +public class LineRange { + int from, to; + + public LineRange(int from, int to) { + if (to < from) { + throw new IllegalArgumentException("Line range is not valid: " + from + " must be greater than " + to); + } + this.from = from; + this.to = to; + } + + // SONAR-ON + public boolean in(int lineId) { + return from <= lineId && lineId <= to; + } + // SONAR-OFF + + public Set<Integer> toLines() { + Set<Integer> lines = Sets.newLinkedHashSet(); + for (int index = from; index <= to; index++) { + lines.add(index); + } + return lines; + } + +}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-double-regexp.txt b/sonar-scanner-engine/src/test/resources/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-double-regexp.txt new file mode 100644 index 00000000000..002169fe031 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-double-regexp.txt @@ -0,0 +1,35 @@ +package org.sonar.plugins.switchoffviolations.pattern; + +import com.google.common.collect.Sets; + +import java.util.Set; + +/** + * + */ +public class LineRange { + int from, to; + + public LineRange(int from, int to) { + if (to < from) { + throw new IllegalArgumentException("Line range is not valid: " + from + " must be greater than " + to); + } + this.from = from; + this.to = to; + } + + // SONAR-OFF + public boolean in(int lineId) { + return from <= lineId && lineId <= to; + } + // SONAR-ON + + public Set<Integer> toLines() { + Set<Integer> lines = Sets.newLinkedHashSet(); + for (int index = from; index <= to; index++) { + lines.add(index); + } + return lines; + } + +}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-no-regexp.txt b/sonar-scanner-engine/src/test/resources/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-no-regexp.txt new file mode 100644 index 00000000000..f18fa5b90ad --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-no-regexp.txt @@ -0,0 +1,33 @@ +package org.sonar.plugins.switchoffviolations.pattern; + +import com.google.common.collect.Sets; + +import java.util.Set; + +/** + * + */ +public class LineRange { + int from, to; + + public LineRange(int from, int to) { + if (to < from) { + throw new IllegalArgumentException("Line range is not valid: " + from + " must be greater than " + to); + } + this.from = from; + this.to = to; + } + + public boolean in(int lineId) { + return from <= lineId && lineId <= to; + } + + public Set<Integer> toLines() { + Set<Integer> lines = Sets.newLinkedHashSet(); + for (int index = from; index <= to; index++) { + lines.add(index); + } + return lines; + } + +}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-single-regexp-and-double-regexp.txt b/sonar-scanner-engine/src/test/resources/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-single-regexp-and-double-regexp.txt new file mode 100644 index 00000000000..e09ecd7a323 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-single-regexp-and-double-regexp.txt @@ -0,0 +1,36 @@ +package org.sonar.plugins.switchoffviolations.pattern; + +import com.google.common.collect.Sets; + + // SONAR-OFF + +import java.util.Set; + +/** + * @SONAR-IGNORE-ALL + */ +public class LineRange { + int from, to; + + public LineRange(int from, int to) { + if (to < from) { + throw new IllegalArgumentException("Line range is not valid: " + from + " must be greater than " + to); + } + this.from = from; + this.to = to; + } + + public boolean in(int lineId) { + return from <= lineId && lineId <= to; + } + // SONAR-ON + + public Set<Integer> toLines() { + Set<Integer> lines = Sets.newLinkedHashSet(); + for (int index = from; index <= to; index++) { + lines.add(index); + } + return lines; + } + +}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-single-regexp.txt b/sonar-scanner-engine/src/test/resources/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-single-regexp.txt new file mode 100644 index 00000000000..ef135ebc50c --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/issue/ignore/scanner/IssueExclusionsRegexpScannerTest/file-with-single-regexp.txt @@ -0,0 +1,33 @@ +package org.sonar.plugins.switchoffviolations.pattern; + +import com.google.common.collect.Sets; + +import java.util.Set; + +/** + * @SONAR-IGNORE-ALL + */ +public class LineRange { + int from, to; + + public LineRange(int from, int to) { + if (to < from) { + throw new IllegalArgumentException("Line range is not valid: " + from + " must be greater than " + to); + } + this.from = from; + this.to = to; + } + + public boolean in(int lineId) { + return from <= lineId && lineId <= to; + } + + public Set<Integer> toLines() { + Set<Integer> lines = Sets.newLinkedHashSet(); + for (int index = from; index <= to; index++) { + lines.add(index); + } + return lines; + } + +}
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/repository/DefaultProjectRepositoriesLoaderTest/project.protobuf b/sonar-scanner-engine/src/test/resources/org/sonar/batch/repository/DefaultProjectRepositoriesLoaderTest/project.protobuf Binary files differnew file mode 100644 index 00000000000..ce579fdbd5e --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/repository/DefaultProjectRepositoriesLoaderTest/project.protobuf diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/repository/DefaultQualityProfileLoaderTest/quality_profile_search_default b/sonar-scanner-engine/src/test/resources/org/sonar/batch/repository/DefaultQualityProfileLoaderTest/quality_profile_search_default Binary files differnew file mode 100644 index 00000000000..6780d7338a1 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/repository/DefaultQualityProfileLoaderTest/quality_profile_search_default diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/rule/DefaultActiveRulesLoaderTest/active_rule_search1.protobuf b/sonar-scanner-engine/src/test/resources/org/sonar/batch/rule/DefaultActiveRulesLoaderTest/active_rule_search1.protobuf Binary files differnew file mode 100644 index 00000000000..5544968df4b --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/rule/DefaultActiveRulesLoaderTest/active_rule_search1.protobuf diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/rule/DefaultActiveRulesLoaderTest/active_rule_search2.protobuf b/sonar-scanner-engine/src/test/resources/org/sonar/batch/rule/DefaultActiveRulesLoaderTest/active_rule_search2.protobuf Binary files differnew file mode 100644 index 00000000000..a23bd1d5d81 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/rule/DefaultActiveRulesLoaderTest/active_rule_search2.protobuf diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/rule/DefaultRulesLoader/response.protobuf b/sonar-scanner-engine/src/test/resources/org/sonar/batch/rule/DefaultRulesLoader/response.protobuf new file mode 100644 index 00000000000..3c24dd83d29 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/rule/DefaultRulesLoader/response.protobuf @@ -0,0 +1,637 @@ + +i +common-javaInsufficientCommentDensity">Source files should have a sufficient density of comment lines +S +common-javaDuplicatedBlocks"2Source files should not have any duplicated blocks +U +common-javaSkippedUnitTests"4Skipped unit tests should be either removed or fixed +\ +common-javaInsufficientLineCoverage"3Lines should have sufficient coverage by unit tests +A +common-javaFailedUnitTests"!Failed unit tests should be fixed +a +common-javaInsufficientBranchCoverage"6Branches should have sufficient coverage by unit tests +“ +squidUselessParenthesesCheckUselessParenthesesCheck"XUseless parentheses around expressions should be removed to prevent any misunderstanding +Z +squidS2134S2134"CClasses extending java.lang.Thread should override the "run" method +; +squidS138S138"&Methods should not have too many lines +G +squidS2133S2133"0Objects should not be created only to "getClass" +M +squidS1294S1294"6The Array.equals(Object obj) method should not be used +R +squidS2131S2131";Primitives should not be boxed just for "String" conversion +` +squidS135S135"KLoops should not contain more than a single "break" or "continue" statement +< +squidS1150S1150"%Enumeration should not be implemented +K +squidS1151S1151"4"switch case" clauses should not have too many lines +M +squidS1939S1939"6Extensions and implementations should not be redundant +E +squidS2039S2039".Member variable visibility should be specified +L +squidNoSonarNoSonar"1"NOSONAR" should not be used to switch off issues +> +squidS2232S2232"'"ResultSet.isLast()" should not be used +f +squidS1943S1943"OClasses and methods that rely on the default system encoding should not be used +m +squidS1158S1158"VPrimitive wrappers should not be instantiated only for "toString" or "compareTo" calls +H +squidS2230S2230"1Non-public methods should not be "@Transactional" +t +squidS1157S1157"]Case insensitive string comparisons should be made without intermediate upper or lower casing +P +squidS1155S1155"9Collection.isEmpty() should be used to test for emptiness +s +squidS2236S2236"\Methods "wait(...)", "notify()" and "notifyAll()" should never be called on Thread instances +b +squidS1948S1948"KFields in a "Serializable" class should either be transient or serializable +J +squidS1153S1153"3String.valueOf() should not be appended to a String +H +squidS2235S2235"1IllegalMonitorStateException should not be caught +b +squidS1764S1764"KIdentical expressions should not be used on both sides of a binary operator +P +squidS2130S2130"9Parsing should be used to convert "Strings" to primitives +s +squidUndocumentedApiUndocumentedApi"HPublic types, methods and fields (API) should be documented with Javadoc += +squidS2333S2333"&Redundant modifiers should not be used +o +squidTrailingCommentCheckTrailingCommentCheck":Comments should not be located at the end of lines of code +m +squidMaximumInheritanceDepthMaximumInheritanceDepth"2Inheritance tree of classes should not be too deep +< +squidS1940S1940"%Boolean checks should not be inverted +L +squidS1699S1699"5Constructors should only call non-overridable methods +T +squidS128S128"?Switch cases should end with an unconditional "break" statement +M +squidS2127S2127"6"Double.longBitsToDouble" should not be used for "int" +X +squidCallToDeprecatedMethodCallToDeprecatedMethod"Avoid use of deprecated methods +] +squidS888S888"HRelational operators should be used in "for" loop termination conditions +A +squidS2123S2123"*Values should not be uselessly incremented +S +squidS2122S2122"<"ScheduledThreadPoolExecutor" should not have 0 core threads +P +squidS1160S1160"9Public methods should throw at most one checked exception + +squidS1161S1161"x"@Override" annotation should be used on any method overriding (since Java 5) or implementing (since Java 6) another one +W +squidS1694S1694"@An abstract class should have both abstract and concrete methods += +squidS1162S1162"&Checked Exception should not be thrown +K +squidS00101S00101"2Class names should comply with a naming convention +M +squidS1695S1695"6"NullPointerException" should not be explicitly thrown +L +squidS00100S00100"3Method names should comply with a naming convention +B +squidS1696S1696"+"NullPointerException" should not be caught +n +squidS1697S1697"WShort-circuit logic should be used to prevent null pointer dereferences in conditionals +A +squidS1698S1698"*Objects should be compared with "equals()" +V +squidS1168S1168"?Empty arrays and collections should be returned instead of null +r +squidStringEqualityComparisonCheckStringEqualityComparisonCheck"+Strings should be compared using "equals()" +/ +squidS2222S2222"Locks should be released +[ +squidHiddenFieldCheckHiddenFieldCheck".Local variables should not shadow class fields +? +squidS2326S2326"(Unused type parameters should be removed +H +squidS1163S1163"1Exceptions should not be thrown in finally blocks +P +squidS2225S2225"9"toString()" and "clone()" methods should not return null +P +squidS1166S1166"9Exception handlers should preserve the original exception +< +squidS1165S1165"%Exception classes should be immutable +I +squidS2226S2226"2Servlets should never have mutable instance fields +S +squidArchitecturalConstraintArchitecturalConstraint"Architectural constraint +u +squidS134S134"`Control flow statements "if", "for", "while", "switch" and "try" should not be nested too deeply +[ +squidS2325S2325"D"private" methods that don't access instance data should be "static" +J +squidS2156S2156"3"final" classes should not have "protected" members +z +squidS2154S2154"cDissimilar primitive wrappers should not be used with the ternary operator without explicit casting +M +squidS2153S2153"6Boxing and unboxing should not be immediately reversed +? +squidS2159S2159"(Silly equality checks should not be made +< +squidS2157S2157"%"Cloneables" should implement "clone" +A +squidS1172S1172"*Unused method parameters should be removed +R +squidS1479S1479";"switch" statements should not have too many "case" clauses + +squidS1170S1170"jPublic constants and fields initialized at declaration should be "static final" rather than merely "final" +D +squidS1171S1171"-Only static class initializers should be used +] +squidS1175S1175"FThe signature of "finalize()" should match that of "Object.finalize()" +b +squidS1174S1174"K"Object.finalize()" should remain protected (versus public) when overriding +A +squidS2151S2151"*"runFinalizersOnExit" should not be called +f +squidCallToFileDeleteOnExitMethodCallToFileDeleteOnExitMethod"!"deleteOnExit" should not be used +Z +squidLabelsShouldNotBeUsedCheckLabelsShouldNotBeUsedCheck"Labels should not be used +e +squidS1488S1488"NLocal Variables should not be declared and then immediately returned or thrown +Z +squidS3008S3008"CStatic non-final field names should comply with a naming convention +{ +squidSwitchLastCaseIsDefaultCheckSwitchLastCaseIsDefaultCheck"6"switch" statements should end with a "default" clause +Ë +squid,RightCurlyBraceDifferentLineAsNextBlockCheck,RightCurlyBraceDifferentLineAsNextBlockCheck"fClose curly brace and the next "else", "catch" and "finally" keywords should be on two different lines +d +squidModifiersOrderCheckModifiersOrderCheck"1Modifiers should be declared in the correct order +? +squidS1181S1181"(Throwable and Error should not be caught +9 +squidS1905S1905""Redundant casts should not be used +c +squidS1182S1182"LClasses that override "clone" should be "Cloneable" and call "super.clone()" +j +squidS2201S2201"SReturn values should not be ignored when function calls don't have any side effects +2 +squidS1186S1186"Methods should not be empty += +squidS1872S1872"&Classes should not be compared by name +R +squidS2438S2438";"Threads" should not be used where "Runnables" are expected +u +squidS1871S1871"^Two branches in the same conditional structure should not have exactly the same implementation +l +squidS1185S1185"UOverriding methods should do more than simply call the same method in the super class +S +squidS1188S1188"<Lambdas and anonymous classes should not have too many lines +@ +squidS1873S1873")"static final" arrays should be "private" +\ +squidS2204S2204"E".equals()" should not be used to test the values of "Atomic" classes +C +squidS2437S2437",Silly bit operations should not be performed +T +squidS2200S2200"="compareTo" results should not be checked for specific values +R +squidUselessImportCheckUselessImportCheck"!Useless imports should be removed +E +squidS2209S2209"."static" members should be accessed statically +? +squidS1481S1481"(Unused local variables should be removed +€ +squidMissingDeprecatedCheckMissingDeprecatedCheck"GDeprecated elements should have both the annotation and the Javadoc tag +: +squidS2208S2208"#Wildcard imports should not be used +> +squidS1774S1774"'The ternary operator should not be used +V +squidS2272S2272"?"Iterator.next()" methods should throw "NoSuchElementException" +‰ +squidS2273S2273"r"wait(...)", "notify()" and "notifyAll()" methods should only be called when a lock is obviously held on an object +a +squidLowerCaseLongSuffixCheckLowerCaseLongSuffixCheck"$Long suffix "L" should be upper case +C +squidS2786S2786",Nested "enum"s should not be declared static +J +squidS1118S1118"3Utility classes should not have public constructors +z +squidS2277S2277"cCryptographic RSA algorithms should always incorporate OAEP (Optimal Asymmetric Encryption Padding) +a +squidUnusedProtectedMethodUnusedProtectedMethod"*Unused protected methods should be removed +d +squidS2276S2276"M"wait(...)" should be used instead of "Thread.sleep(...)" when a lock is held +d +squidS2275S2275"MPrintf-style format strings should not lead to unexpected behavior at runtime +k +squidS2274S2274"T"Object.wait(...)" and "Condition.await(...)" should be called inside a "while" loop +c +squidCommentedOutCodeLineCommentedOutCodeLine".Sections of code should not be "commented out" +^ +squidS2278S2278"GNeither DES (Data Encryption Standard) nor DESede (3DES) should be used +C +squidS2912S2912","indexOf" checks should use a start position +< +squidS1656S1656"%Variables should not be self-assigned +Q +squidS1659S1659":Multiple variables should not be declared on the same line +L +squidS1264S1264"5A "while" loop should be used instead of a "for" loop +Y +squidS1125S1125"BLiteral boolean values should not be used in condition expressions +k +squidS1126S1126"TReturn of boolean expressions should not be wrapped into an "if-then-else" statement + +squidClassVariableVisibilityCheckClassVariableVisibilityCheck":Class variable fields should not have public accessibility +H +squidS2250S2250"1"ConcurrentLinkedQueue.size()" should not be used +M +squidS1643S1643"6Strings should not be concatenated using '+' in a loop +` +squidS2251S2251"IA "for" loop update clause should move the counter in the right direction +Z +squidS1640S1640"CMaps with keys that are enum values should be replaced with EnumMap +E +squidS2118S2118".Non-serializable classes should not be written +B +squidS00112S00112")Generic exceptions should never be thrown +> +squidS2111S2111"'"BigDecimal(double)" should not be used +H +squidS2112S2112"1"URL.hashCode" and "URL.equals" should be avoided +? +squidS2110S2110"(Invalid "Date" values should not be used +X +squidS2116S2116"A"hashCode" and "toString" should not be called on array instances +J +squidS2391S2391"3JUnit framework methods should be declared properly +Y +squidS2114S2114"BCollections should not be passed as arguments to their own methods +? +squidS2259S2259"(Null pointers should not be dereferenced +d +squidS1132S1132"MStrings literals should be placed on the left side when checking for equality +D +squidS00107S00107"+Methods should not have too many parameters +c +squidS2258S2258"L"javax.crypto.NullCipher" should not be used for anything other than testing +C +squidS1133S1133",Deprecated code should be removed eventually +L +squidS2257S2257"5Only standard cryptographic algorithms should be used +G +squidS00108S00108".Nested blocks of code should not be left empty +5 +squidS00103S00103"Lines should not be too long +V +squidS2254S2254"?"HttpServletRequest.getRequestedSessionId()" should not be used +< +squidS2253S2253"%Disallowed methods should not be used +5 +squidS1134S1134""FIXME" tags should be handled +A +squidS00105S00105"(Tabulation characters should not be used +C +squidS2252S2252",Loop conditions should be true at least once +4 +squidS1135S1135""TODO" tags should be handled += +squidS00104S00104"$Files should not have too many lines +? +squidS00122S00122"&Statements should be on separate lines +M +squidS00120S00120"4Package names should comply with a naming convention +U +squidS2109S2109">Reflection should not be used to check non-runtime annotations +J +squidS00121S00121"1Control structures should always use curly braces +j +squidS2676S2676"SNeither "Math.abs" nor negation should be used on numbers that could be "MIN_VALUE" +I +squidS2677S2677"2"read" and "readLine" return values should be used +N +squidS2674S2674"7The value returned from a stream read should be checked +@ +squidS2675S2675")"readObject" should not be "synchronized" +V +squidCycleBetweenPackagesCycleBetweenPackages"!Avoid cycle between java packages +h +squidS1149S1149"QSynchronized classes Vector, Hashtable, Stack and StringBuffer should not be used +O +squidS1244S1244"8Floating point numbers should not be tested for equality +P +squidS2384S2384"9Mutable members should not be stored or returned directly +{ +squidLeftCurlyBraceEndLineCheckLeftCurlyBraceEndLineCheck":An open curly brace should be located at the end of a line +Q +squidS2387S2387":Child class members should not shadow parent class members +P +squidS2386S2386"9Interfaces should not have "public static" mutable fields +U +squidS2388S2388">Inner class calls to super class methods should be unambiguous +< +squidS1141S1141"%Try-catch blocks should not be nested +L +squidS1142S1142"5Methods should not contain too many return statements +T +squidS00119S00119";Type parameter names should comply with a naming convention +c +squidS2245S2245"LPseudorandom number generators (PRNGs) should not be used in secure contexts +O +squidS1143S1143"8"return" statements should not occur in "finally" blocks +T +squidS00118S00118";Abstract class names should comply with a naming convention +i +squidS00117S00117"PLocal variable and method parameter names should comply with a naming convention +] +squidS1145S1145"FUseless "if(true) {...}" and "if(false){...}" blocks should be removed +K +squidS00116S00116"2Field names should comply with a naming convention +N +squidS00115S00115"5Constant names should comply with a naming convention +… +squidLeftCurlyBraceStartLineCheckLeftCurlyBraceStartLineCheck"@An open curly brace should be located at the beginning of a line +8 +squidS1147S1147"!Exit methods should not be called +O +squidS00114S00114"6Interface names should comply with a naming convention +J +squidS1148S1148"3Throwable.printStackTrace(...) should not be called +J +squidS00113S00113"1Files should contain an empty new line at the end +] +squidS1215S1215"FExecution of the Garbage Collector should be triggered only by the JVM +T +squidS1217S1217"=Thread.run() and Runnable.run() should not be called directly +A +squidS2188S2188"*JUnit test cases should call super methods +M +squidS1219S1219"6"switch" statements should not contain non-case labels +K +squidS2186S2186"4JUnit assertions should not be used in "run" methods +5 +squidS2187S2187"TestCases should contain tests +i +squidS1210S1210"R"equals(Object obj)" should be overridden along with the "compareTo(T obj)" method +D +squidS1214S1214"-Constants should not be defined in interfaces +o +squidS1609S1609"X@FunctionalInterface annotation should be used to flag Single Abstract Method interfaces +l +squidS1213S1213"UThe members of an interface declaration or class should appear in a pre-defined order +d +squidObjectFinalizeCheckObjectFinalizeCheck"1The Object.finalize() method should not be called +< +squidS2089S2089"%HTTP referers should not be relied on +s +squidS1611S1611"\Parentheses should be removed from a single lambda input parameter when its type is inferred +J +squidS2681S2681"3Multiline blocks should be enclosed in curly braces +K +squidS1612S1612"4Replace lambdas with method references when possible +9 +squidS2185S2185""Silly math should not be performed +E +squidS2184S2184".Math operands should be cast before assignment +X +squidS1610S1610"AAbstract classes without fields should be converted to interfaces +_ +squidS2183S2183"HInts and longs should not be shifted by more than their number of bits-1 +8 +squid EmptyFile EmptyFile"Files should not be empty +N +squidS1228S1228"7Packages should have a javadoc file 'package-info.java' +Z +squidUnusedPrivateMethodUnusedPrivateMethod"'Unused private method should be removed +j +squidS1226S1226"SMethod parameters, caught exceptions and foreach variables should not be reassigned +P +squidS2197S2197"9Modulus results should not be checked for direct equality +ƒ +squidAssignmentInSubExpressionCheckAssignmentInSubExpressionCheck":Assignments should not be made from within sub-expressions +H +squidS1221S1221"1Methods should not be named "hashcode" or "equal" +E +squidS1220S1220".The default unnamed package should not be used +1 +squidS2092S2092"Cookies should be "secure" +2 +squidS2094S2094"Classes should not be empty +1 +squidS2095S2095"Resources should be closed +9 +squidS2096S2096"""main" should not "throw" anything +c +squidS1223S1223"LNon-constructor methods should not have the same name as the enclosing class +E +squidS2097S2097"."equals(Object obj)" should test argument type +K +squidS2696S2696"4Instance methods should not write to "static" fields +6 +squidS2699S2699"Tests should include assertions +? +squidS2698S2698"(JUnit assertions should include messages +D +squidS2693S2693"-Threads should not be started in constructors +J +squidS2692S2692"3"indexOf" checks should not be for positive numbers +f +squidS2695S2695"O"PreparedStatement" and "ResultSet" methods should be called with valid indices +c +squidS2694S2694"LInner classes which do not reference their owning classes should be "static" +I +squidS2885S2885"2"Calendars" and "DateFormats" should not be static +] +squidS2166S2166"FClasses named like "Exception" should extend "Exception" or a subclass +H +squidS2167S2167"1"compareTo" should not return "Integer.MIN_VALUE" += +squidS2164S2164"&Math should not be performed on floats +A +squidS2165S2165"*"finalize" should not set fields to "null" +s +squidS1994S1994"\"for" loop incrementers should modify the variable being tested in the loop's stop condition +x +squidRedundantThrowsDeclarationCheckRedundantThrowsDeclarationCheck"-Throws declarations should not be superfluous +S +squidS2162S2162"<"equals" methods should be symmetric and work for subclasses +J +squidS2160S2160"3Subclasses that add fields should override "equals" +J +squidS2175S2175"3Inappropriate "Collection" calls should not be made +o +squidForLoopCounterChangedCheckForLoopCounterChangedCheck"."for" loop stop conditions should be invariant +O +squidS2176S2176"8Class names should not shadow interfaces or superclasses +M +squidS2178S2178"6Short-circuit logic should be used in boolean contexts +T +squidS1700S1700"=A field should not duplicate the name of its containing class +Z +squidS1206S1206"C"equals(Object obj)" and "hashCode()" should be overridden in pairs +K +squidS1701S1701"4Fields and methods should not have conflicting names +S +squidS1201S1201"<Methods named "equals" should override Object.equals(Object) +p +squidS1200S1200"YClasses should not be coupled to too many other classes (Single Responsibility Principle) +` +squidClassCyclomaticComplexityClassCyclomaticComplexity"!Classes should not be too complex +f +squidS1602S1602"OLamdbas containing only one statement should not nest this statement in a block +_ +squidS1604S1604"HAnonymous inner classes containing only one method should become lambdas +J +squidS1309S1309"3The @SuppressWarnings annotation should not be used +K +squidS1607S1607"4Skipped unit tests should be either removed or fixed +P +squidS1301S1301"9"switch" statements should have at least 3 "case" clauses +1 +squidS2446S2446""notifyAll" should be used +r +squidS2445S2445"[Blocks synchronized on fields should not contain assignments of new objects to those fields +V +squidS2444S2444"?Lazy initialization of "static" fields should be "synchronized" +M +squidS1858S1858"6"toString()" should never be called on a String object +y +squidObjectFinalizeOverridenCheckObjectFinalizeOverridenCheck"4The Object.finalize() method should not be overriden +B +squidS2442S2442"+"Lock" objects should not be "synchronized" +V +squidS2441S2441"?Non-serializable objects should not be stored in "HttpSessions" +T +squidS2440S2440"=Classes with only "static" methods should not be instantiated +C +squidS1710S1710",Annotation repetitions should not be wrapped +] +squidS2583S2583"FConditions should not unconditionally evaluate to "TRUE" or to "FALSE" +d +squidS1850S1850"M"instanceof" operators that always return "true" or "false" should be removed +J +squidS2447S2447"3Null should not be returned from a "Boolean" method +F +squidS1310S1310"/"NOPMD" suppression comments should not be used +d +squidS864S864"OLimited dependence should be placed on operator precedence rules in expressions +; +squidS1313S1313"$IP addresses should not be hardcoded +d +squidS1312S1312"MLoggers should be "private static final" and should share a naming convention +š +squidS1319S1319"‚Declarations should use Java collection interfaces such as "List" rather than specific implementation classes such as "LinkedList" +W +squidS1318S1318"@"object == null" should be used instead of "object.equals(null)" +U +squidS1452S1452">Generic wildcard types should not be used in return parameters +F +squidS1451S1451"/Copyright and license headers should be defined +X +squidIndentationCheckIndentationCheck"+Source code should be indented consistently +_ +squidS2718S2718"H"DateUtils.truncate" from Apache Commons Lang library should not be used +O +squidS1315S1315"8"CHECKSTYLE:OFF" suppression comments should not be used +6 +squidS1314S1314"Octal values should not be used +e +squidS1317S1317"N"StringBuilder" and "StringBuffer" should not be instantiated with a character +Y +squidS1860S1860"BSynchronization should not be based on Strings or boxed primitives +Y +squidS1862S1862"BRelated "if/else if" statements should not have the same condition +[ +squidS1724S1724"DDeprecated classes and interfaces should not be extended/implemented +z +squidS881S881"eIncrement (++) and decrement (--) operators should not be mixed with other operators in an expression +8 +squidParsingErrorParsingError"Java parser failure +M +squidS1598S1598"6Package declaration should match source file directory +u +squidS2055S2055"^The non-serializable super class of a "Serializable" class must have a no-argument constructor +“ +squidS1596S1596"|Collections.emptyList(), emptyMap() and emptySet() should be used instead of Collections.EMPTY_LIST, EMPTY_MAP and EMPTY_SET +F +squidS2057S2057"/"Serializable" classes should have a version id +^ +squidS2059S2059"G"Serializable" inner classes of "Serializable" classes should be static +N +squidS2701S2701"7Literal boolean values should not be used in assertions +; +squidS2063S2063"$Comparators should be "Serializable" +Ã +squid'RightCurlyBraceSameLineAsNextBlockCheck'RightCurlyBraceSameLineAsNextBlockCheck"hClose curly brace and the next "else", "catch" and "finally" keywords should be located on the same line +V +squidS2061S2061"?Custom serialization method signatures should meet requirements +C +squidS1066S1066",Collapsible "if" statements should be merged +< +squidS1067S1067"%Expressions should not be too complex +_ +squidEmptyStatementUsageCheckEmptyStatementUsageCheck""Empty statements should be removed +b +squidMethodCyclomaticComplexityMethodCyclomaticComplexity"!Methods should not be too complex +6 +squidS1065S1065"Unused labels should be removed +> +squidS1068S1068"'Unused private fields should be removed +U +squidS2976S2976">"File.createTempFile" should not be used to create a directory +N +squidS2974S2974"7Classes without "public" constructors should be "final" +; +squidS2068S2068"$Credentials should not be hard-coded +4 +squidS2970S2970"Assertions should be complete +S +squidS2065S2065"<Fields in non-serializable classes should not be "transient" +b +squidS2066S2066"K"Serializable" inner classes of non-serializable classes should be "static" +U +squidS1197S1197">Array designators "[]" should be on the type, not the variable + +squidS1844S1844"j"Object.wait(...)" should never be called on objects that implement "java.util.concurrent.locks.Condition" +< +squidS1199S1199"%Nested code blocks should not be used +a +squidS1848S1848"JObjects should not be created to be dropped immediately without being used +K +squidS2301S2301"4Public methods should not contain selector arguments +M +squidS1849S1849"6"Iterator.hasNext()" should not call "Iterator.next()" +R +squidS2070S2070";SHA-1 and Message-Digest hash algorithms should not be used +‡ +squidRightCurlyBraceStartLineCheckRightCurlyBraceStartLineCheck"@A close curly brace should be located at the beginning of a line +] +squidS2864S2864"F"entrySet()" should be iterated when both the key and value are needed + +squidS1444S1444": +G +squidS1191S1191"0Classes from "sun.*" packages should not be used +B +squidS1190S1190"+Future keywords should not be used as names +^ +squidS1193S1193"GException types should not be tested using "instanceof" in catch blocks +G +squidS2076S2076"0Values passed to OS commands should be sanitized +H +squidS2077S2077"1Values passed to SQL commands should be sanitized +5 +squidS109S109" Magic numbers should not be used +? +squidS1192S1192"(String literals should not be duplicated +P +squidS106S106";Standard ouputs should not be used directly to log anything +c +squidS1195S1195"LArray designators "[]" should be located after the type in method signatures +H +squidS2078S2078"1Values passed to LDAP queries should be sanitized +º +squid.ObjectFinalizeOverridenCallsSuperFinalizeCheck.ObjectFinalizeOverridenCallsSuperFinalizeCheck"Qsuper.finalize() should be called at the end of Object.finalize() implementations +? +squidS1194S1194"("java.lang.Error" should not be extended
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/rule/ModuleQProfilesTest/shared.xml b/sonar-scanner-engine/src/test/resources/org/sonar/batch/rule/ModuleQProfilesTest/shared.xml new file mode 100644 index 00000000000..feb234f20e8 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/rule/ModuleQProfilesTest/shared.xml @@ -0,0 +1,19 @@ +<dataset> + + <rules_profiles id="1" name="Java One" language="java" parent_kee="[null]" kee="java-one" is_default="[false]" + created_at="2014-01-20" updated_at="2014-01-20" + rules_updated_at="2014-01-20T12:00:00+0000"/> + + <rules_profiles id="2" name="Java Two" language="java" parent_kee="[null]" kee="java-two" is_default="[false]" + created_at="2014-01-20" updated_at="2014-01-20" + rules_updated_at="2014-01-20T12:00:00+0000"/> + + <rules_profiles id="3" name="Php One" language="php" parent_kee="[null]" kee="php-one" is_default="[false]" + created_at="2014-01-20" updated_at="2014-01-20" + rules_updated_at="2014-01-20T12:00:00+0000"/> + + <rules_profiles id="4" name="Cobol One" language="cbl" parent_kee="[null]" kee="cobol-one" is_default="[false]" + created_at="2014-01-20" updated_at="2014-01-20" + rules_updated_at="2014-01-20T12:00:00+0000"/> + +</dataset> diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module-inherited/module1/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module-inherited/module1/sonar-project.properties new file mode 100644 index 00000000000..7bace22a204 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module-inherited/module1/sonar-project.properties @@ -0,0 +1 @@ +# Mandatory properties for module1 are all inferred from the module ID diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module-inherited/module1/sources/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module-inherited/module1/sources/Fake.java new file mode 100644 index 00000000000..9d445f04fc6 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module-inherited/module1/sources/Fake.java @@ -0,0 +1,5 @@ + +class Fake { + + +} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module-inherited/module2/newBaseDir/src/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module-inherited/module2/newBaseDir/src/Fake.java new file mode 100644 index 00000000000..22d579be381 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module-inherited/module2/newBaseDir/src/Fake.java @@ -0,0 +1 @@ +class Fake { } diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module-inherited/module2/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module-inherited/module2/sonar-project.properties new file mode 100644 index 00000000000..d25a9e9e1f5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module-inherited/module2/sonar-project.properties @@ -0,0 +1,6 @@ +sonar.projectKey=com.foo.project.module2 +sonar.projectName=Foo Module 2 +# redefine some properties +sonar.projectBaseDir=newBaseDir +sonar.projectDescription=Description of Module 2 +sonar.sources=src diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module-inherited/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module-inherited/sonar-project.properties new file mode 100644 index 00000000000..4744284e7bf --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module-inherited/sonar-project.properties @@ -0,0 +1,11 @@ +sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project + +sonar.sources=sources +sonar.tests=tests +sonar.binaries=target/classes + +sonar.modules=module1,\ + module2 diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module/module1/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module/module1/sonar-project.properties new file mode 100644 index 00000000000..ec642a9443a --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module/module1/sonar-project.properties @@ -0,0 +1,4 @@ +sonar.projectKey=com.foo.project.module1 +sonar.projectName=Foo Module 1 +sonar.projectDescription=Description of Module 1 +sonar.sources=sources diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module/module1/sources/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module/module1/sources/Fake.java new file mode 100644 index 00000000000..e67004defc5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module/module1/sources/Fake.java @@ -0,0 +1 @@ +class Fake {} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module/module2/newBaseDir/src/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module/module2/newBaseDir/src/Fake.java new file mode 100644 index 00000000000..e67004defc5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module/module2/newBaseDir/src/Fake.java @@ -0,0 +1 @@ +class Fake {} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module/module2/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module/module2/sonar-project.properties new file mode 100644 index 00000000000..d25a9e9e1f5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module/module2/sonar-project.properties @@ -0,0 +1,6 @@ +sonar.projectKey=com.foo.project.module2 +sonar.projectName=Foo Module 2 +# redefine some properties +sonar.projectBaseDir=newBaseDir +sonar.projectDescription=Description of Module 2 +sonar.sources=src diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module/sonar-project.properties new file mode 100644 index 00000000000..2f16886c91d --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-definitions-in-each-module/sonar-project.properties @@ -0,0 +1,10 @@ +sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project + +sonar.tests=tests +sonar.binaries=target/classes + +sonar.modules=module1,\ + module2 diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-with-configfile-and-overwritten-basedir/any-folder/generated/any-file.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-with-configfile-and-overwritten-basedir/any-folder/generated/any-file.properties new file mode 100644 index 00000000000..c50d50b50f7 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-with-configfile-and-overwritten-basedir/any-folder/generated/any-file.properties @@ -0,0 +1,5 @@ +sonar.projectKey=com.foo.project.module1 +sonar.projectName=Foo Module 1 + +# and specify a different baseDir +sonar.projectBaseDir=.. diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-with-configfile-and-overwritten-basedir/any-folder/sources/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-with-configfile-and-overwritten-basedir/any-folder/sources/Fake.java new file mode 100644 index 00000000000..e67004defc5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-with-configfile-and-overwritten-basedir/any-folder/sources/Fake.java @@ -0,0 +1 @@ +class Fake {} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-with-configfile-and-overwritten-basedir/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-with-configfile-and-overwritten-basedir/sonar-project.properties new file mode 100644 index 00000000000..c1640b15cba --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-with-configfile-and-overwritten-basedir/sonar-project.properties @@ -0,0 +1,12 @@ +sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project + +sonar.sources=sources +sonar.tests=tests +sonar.binaries=target/classes + +sonar.modules=module1 + +module1.sonar.projectConfigFile=any-folder/generated/any-file.properties diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-with-configfile/any-folder/any-file.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-with-configfile/any-folder/any-file.properties new file mode 100644 index 00000000000..460d3495a89 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-with-configfile/any-folder/any-file.properties @@ -0,0 +1,2 @@ +sonar.projectKey=com.foo.project.module1 +sonar.projectName=Foo Module 1 diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-with-configfile/any-folder/sources/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-with-configfile/any-folder/sources/Fake.java new file mode 100644 index 00000000000..e67004defc5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-with-configfile/any-folder/sources/Fake.java @@ -0,0 +1 @@ +class Fake {} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-with-configfile/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-with-configfile/sonar-project.properties new file mode 100644 index 00000000000..e246f8c2af8 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-with-configfile/sonar-project.properties @@ -0,0 +1,12 @@ +sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project + +sonar.sources=sources +sonar.tests=tests +sonar.binaries=target/classes + +sonar.modules=module1 + +module1.sonar.projectConfigFile=any-folder/any-file.properties diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-with-unexisting-file/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-with-unexisting-file/sonar-project.properties new file mode 100644 index 00000000000..e246f8c2af8 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/DeprecatedProjectReactorBuilderTest/multi-module-with-unexisting-file/sonar-project.properties @@ -0,0 +1,12 @@ +sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project + +sonar.sources=sources +sonar.tests=tests +sonar.binaries=target/classes + +sonar.modules=module1 + +module1.sonar.projectConfigFile=any-folder/any-file.properties diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/big-multi-module-definitions-all-in-root/module1/module11/sources/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/big-multi-module-definitions-all-in-root/module1/module11/sources/Fake.java new file mode 100644 index 00000000000..e67004defc5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/big-multi-module-definitions-all-in-root/module1/module11/sources/Fake.java @@ -0,0 +1 @@ +class Fake {} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/big-multi-module-definitions-all-in-root/module1/module12/sources/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/big-multi-module-definitions-all-in-root/module1/module12/sources/Fake.java new file mode 100644 index 00000000000..e67004defc5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/big-multi-module-definitions-all-in-root/module1/module12/sources/Fake.java @@ -0,0 +1 @@ +class Fake {} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/big-multi-module-definitions-all-in-root/module2/sources/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/big-multi-module-definitions-all-in-root/module2/sources/Fake.java new file mode 100644 index 00000000000..e67004defc5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/big-multi-module-definitions-all-in-root/module2/sources/Fake.java @@ -0,0 +1 @@ +class Fake {} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/big-multi-module-definitions-all-in-root/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/big-multi-module-definitions-all-in-root/sonar-project.properties new file mode 100644 index 00000000000..e14567d26af --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/big-multi-module-definitions-all-in-root/sonar-project.properties @@ -0,0 +1,17 @@ +sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project + +sonar.sources=sources +sonar.tests=tests +sonar.binaries=target/classes +sonar.profile=Foo + +sonar.modules=module1,module2 + + +module1.sonar.modules=module11,module12 + +module1.module11.property=My module11 property + diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-language-definitions-all-in-root/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-language-definitions-all-in-root/sonar-project.properties new file mode 100644 index 00000000000..53aacb54061 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-language-definitions-all-in-root/sonar-project.properties @@ -0,0 +1,13 @@ +sonar.projectKey=example +sonar.projectName=Example +sonar.projectVersion=1.0 + +sonar.modules=java-module,groovy-module + +java-module.sonar.language=java +java-module.sonar.projectBaseDir=. +java-module.sonar.sources=src/main/java + +groovy-module.sonar.language=groovy +groovy-module.sonar.projectBaseDir=. +groovy-module.sonar.sources=src/main/groovy diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-language-definitions-all-in-root/src/main/groovy/Fake.groovy b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-language-definitions-all-in-root/src/main/groovy/Fake.groovy new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-language-definitions-all-in-root/src/main/groovy/Fake.groovy diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-language-definitions-all-in-root/src/main/java/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-language-definitions-all-in-root/src/main/java/Fake.java new file mode 100644 index 00000000000..e67004defc5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-language-definitions-all-in-root/src/main/java/Fake.java @@ -0,0 +1 @@ +class Fake {} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-all-in-root/module1/sources/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-all-in-root/module1/sources/Fake.java new file mode 100644 index 00000000000..e67004defc5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-all-in-root/module1/sources/Fake.java @@ -0,0 +1 @@ +class Fake {} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-all-in-root/module2/src/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-all-in-root/module2/src/Fake.java new file mode 100644 index 00000000000..e67004defc5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-all-in-root/module2/src/Fake.java @@ -0,0 +1 @@ +class Fake {} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-all-in-root/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-all-in-root/sonar-project.properties new file mode 100644 index 00000000000..0f06d31466e --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-all-in-root/sonar-project.properties @@ -0,0 +1,19 @@ +sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project + +sonar.sources=sources +sonar.tests=tests +sonar.binaries=target/classes + +sonar.modules=module1,\ + module2 + +# Mandatory properties for module1 are all inferred from the module ID + +module2.sonar.projectKey=com.foo.project.module2 +module2.sonar.projectName=Foo Module 2 +# redefine some properties +module2.sonar.projectDescription=Description of Module 2 +module2.sonar.sources=src diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-moduleKey/module1/sources/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-moduleKey/module1/sources/Fake.java new file mode 100644 index 00000000000..e67004defc5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-moduleKey/module1/sources/Fake.java @@ -0,0 +1 @@ +class Fake {} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-moduleKey/module2/src/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-moduleKey/module2/src/Fake.java new file mode 100644 index 00000000000..e67004defc5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-moduleKey/module2/src/Fake.java @@ -0,0 +1 @@ +class Fake {} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-moduleKey/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-moduleKey/sonar-project.properties new file mode 100644 index 00000000000..6def8070d6f --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-moduleKey/sonar-project.properties @@ -0,0 +1,19 @@ +sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project + +sonar.sources=sources +sonar.tests=tests +sonar.binaries=target/classes + +sonar.modules=module1,\ + module2 + +# Mandatory properties for module1 are all inferred from the module ID + +module2.sonar.moduleKey=com.foo.project.module2 +module2.sonar.projectName=Foo Module 2 +# redefine some properties +module2.sonar.projectDescription=Description of Module 2 +module2.sonar.sources=src diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-same-prefix/module1.feature/src/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-same-prefix/module1.feature/src/Fake.java new file mode 100644 index 00000000000..e67004defc5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-same-prefix/module1.feature/src/Fake.java @@ -0,0 +1 @@ +class Fake {} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-same-prefix/module1/sources/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-same-prefix/module1/sources/Fake.java new file mode 100644 index 00000000000..e67004defc5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-same-prefix/module1/sources/Fake.java @@ -0,0 +1 @@ +class Fake {} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-same-prefix/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-same-prefix/sonar-project.properties new file mode 100644 index 00000000000..2a6221a3757 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-definitions-same-prefix/sonar-project.properties @@ -0,0 +1,19 @@ +sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project + +sonar.sources=sources +sonar.tests=tests +sonar.binaries=target/classes + +sonar.modules=module1,\ + module1.feature + +# Mandatory properties for module1 are all inferred from the module ID + +module1.feature.sonar.projectKey=com.foo.project.module1.feature +module1.feature.sonar.projectName=Foo Module 1 Feature +# redefine some properties +module1.feature.sonar.projectDescription=Description of Module 1 Feature +module1.feature.sonar.sources=src diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-duplicate-id/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-duplicate-id/sonar-project.properties new file mode 100644 index 00000000000..0a971805d13 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-duplicate-id/sonar-project.properties @@ -0,0 +1,12 @@ +sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project + +sonar.sources=sources +sonar.tests=tests +sonar.binaries=target/classes + +sonar.modules=module1,module1 + +module1.sonar.sources=src diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-repeated-id/modules/module1/module1/sources/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-repeated-id/modules/module1/module1/sources/Fake.java new file mode 100644 index 00000000000..e67004defc5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-repeated-id/modules/module1/module1/sources/Fake.java @@ -0,0 +1 @@ +class Fake {} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-repeated-id/modules/module1/sources/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-repeated-id/modules/module1/sources/Fake.java new file mode 100644 index 00000000000..e67004defc5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-repeated-id/modules/module1/sources/Fake.java @@ -0,0 +1 @@ +class Fake {} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-repeated-id/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-repeated-id/sonar-project.properties new file mode 100644 index 00000000000..e63d3da926c --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-repeated-id/sonar-project.properties @@ -0,0 +1,17 @@ +sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project + +sonar.sources=sources +sonar.tests=tests +sonar.binaries=target/classes + +sonar.modules=module1 + +module1.sonar.projectBaseDir=modules/module1 +module1.sonar.projectName=Foo Module 1 +module1.sonar.modules=module1 + +module1.module1.sonar.projectBaseDir=module1 +module1.module1.sonar.projectName=Foo Sub Module 1 diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-basedir-not-associated/modules/module1/sources/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-basedir-not-associated/modules/module1/sources/Fake.java new file mode 100644 index 00000000000..e67004defc5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-basedir-not-associated/modules/module1/sources/Fake.java @@ -0,0 +1 @@ +class Fake {} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-basedir-not-associated/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-basedir-not-associated/sonar-project.properties new file mode 100644 index 00000000000..c572ef1f9e5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-basedir-not-associated/sonar-project.properties @@ -0,0 +1,14 @@ +#sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project + +sonar.sources=sources +sonar.tests=tests +sonar.binaries=target/classes + +sonar.modules=module1 + +module1.sonar.projectBaseDir=modules/module1 +module1.sonar.projectKey=com.foo.project.module1 +module1.sonar.projectName=Foo Module 1 diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-basedir/modules/module1/sources/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-basedir/modules/module1/sources/Fake.java new file mode 100644 index 00000000000..e67004defc5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-basedir/modules/module1/sources/Fake.java @@ -0,0 +1 @@ +class Fake {} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-basedir/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-basedir/sonar-project.properties new file mode 100644 index 00000000000..615f5c77a7f --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-basedir/sonar-project.properties @@ -0,0 +1,14 @@ +sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project + +sonar.sources=sources +sonar.tests=tests +sonar.binaries=target/classes + +sonar.modules=module1 + +module1.sonar.projectBaseDir=modules/module1 +module1.sonar.projectKey=com.foo.project.module1 +module1.sonar.projectName=Foo Module 1 diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-explicit-unexisting-test-dir/module1/src/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-explicit-unexisting-test-dir/module1/src/Fake.java new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-explicit-unexisting-test-dir/module1/src/Fake.java @@ -0,0 +1 @@ + diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-explicit-unexisting-test-dir/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-explicit-unexisting-test-dir/sonar-project.properties new file mode 100644 index 00000000000..09cb2208fcd --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-explicit-unexisting-test-dir/sonar-project.properties @@ -0,0 +1,9 @@ +sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project + +sonar.sources=src + +sonar.modules=module1 +module1.sonar.tests=tests diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-unexisting-basedir/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-unexisting-basedir/sonar-project.properties new file mode 100644 index 00000000000..67fbf347c0e --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-unexisting-basedir/sonar-project.properties @@ -0,0 +1,13 @@ +sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project + +sonar.sources=sources +sonar.tests=tests +sonar.binaries=target/classes + +sonar.modules=module1 + +module1.sonar.projectKey=com.foo.project.module1 +module1.sonar.projectName=Foo Module 1 diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-unexisting-source-dir/module1/src/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-unexisting-source-dir/module1/src/Fake.java new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-unexisting-source-dir/module1/src/Fake.java @@ -0,0 +1 @@ + diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-unexisting-source-dir/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-unexisting-source-dir/sonar-project.properties new file mode 100644 index 00000000000..04ea08a89a1 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/multi-module-with-unexisting-source-dir/sonar-project.properties @@ -0,0 +1,8 @@ +sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project + +sonar.sources=unexisting-source-dir + +sonar.modules=module1 diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/shouldGetFile/foo.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/shouldGetFile/foo.properties new file mode 100644 index 00000000000..8fbb104c92d --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/shouldGetFile/foo.properties @@ -0,0 +1,4 @@ +prop= foo, bar, \ +toto,\ +\ +tutu,
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/shouldGetList/foo.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/shouldGetList/foo.properties new file mode 100644 index 00000000000..8fbb104c92d --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/shouldGetList/foo.properties @@ -0,0 +1,4 @@ +prop= foo, bar, \ +toto,\ +\ +tutu,
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-blank-source-dir/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-blank-source-dir/sonar-project.properties new file mode 100644 index 00000000000..ba79992d5a0 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-blank-source-dir/sonar-project.properties @@ -0,0 +1,6 @@ +sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project + +sonar.sources= diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-build-dir/build/report.txt b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-build-dir/build/report.txt new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-build-dir/build/report.txt diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-build-dir/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-build-dir/sonar-project.properties new file mode 100644 index 00000000000..35b33996ea7 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-build-dir/sonar-project.properties @@ -0,0 +1,8 @@ +sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project +sonar.projectBuildDir=build + +sonar.sources=sources + diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-build-dir/sources/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-build-dir/sources/Fake.java new file mode 100644 index 00000000000..aee03e60b4a --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-build-dir/sources/Fake.java @@ -0,0 +1,3 @@ +package org.sonar.runner.batch.ProjectReactorBuilderTest.simple; + +class Fake {} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-lib-dir/lib/Fake.class b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-lib-dir/lib/Fake.class new file mode 100644 index 00000000000..bf2c3a09e07 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-lib-dir/lib/Fake.class @@ -0,0 +1,3 @@ +package org.sonar.runner.batch.ProjectReactorBuilderTest.simple + +Fake
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-lib-dir/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-lib-dir/sonar-project.properties new file mode 100644 index 00000000000..0cada50b51f --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-lib-dir/sonar-project.properties @@ -0,0 +1,7 @@ +sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project + +sonar.sources=sources +sonar.libraries=lib diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-lib-dir/sources/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-lib-dir/sources/Fake.java new file mode 100644 index 00000000000..aee03e60b4a --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-lib-dir/sources/Fake.java @@ -0,0 +1,3 @@ +package org.sonar.runner.batch.ProjectReactorBuilderTest.simple; + +class Fake {} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-missing-source-dir/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-missing-source-dir/sonar-project.properties new file mode 100644 index 00000000000..3a7a65335dc --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-missing-source-dir/sonar-project.properties @@ -0,0 +1,5 @@ +sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project + diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-unexisting-binary/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-unexisting-binary/sonar-project.properties new file mode 100644 index 00000000000..55d1ddf0242 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-unexisting-binary/sonar-project.properties @@ -0,0 +1,7 @@ +sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project + +sonar.sources=sources +sonar.binaries=bin diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-unexisting-binary/sources/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-unexisting-binary/sources/Fake.java new file mode 100644 index 00000000000..e67004defc5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-unexisting-binary/sources/Fake.java @@ -0,0 +1 @@ +class Fake {} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-unexisting-lib/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-unexisting-lib/sonar-project.properties new file mode 100644 index 00000000000..69ccd8d1411 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-unexisting-lib/sonar-project.properties @@ -0,0 +1,7 @@ +sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project + +sonar.sources=sources +sonar.libraries=libs/*.txt diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-unexisting-lib/sources/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-unexisting-lib/sources/Fake.java new file mode 100644 index 00000000000..e67004defc5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-unexisting-lib/sources/Fake.java @@ -0,0 +1 @@ +class Fake {} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-unexisting-source-dir/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-unexisting-source-dir/sonar-project.properties new file mode 100644 index 00000000000..0b83b11f29c --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-unexisting-source-dir/sonar-project.properties @@ -0,0 +1,6 @@ +sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project + +sonar.sources=unexisting-source-dir diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-unexisting-test-dir/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-unexisting-test-dir/sonar-project.properties new file mode 100644 index 00000000000..a4fac8e4ca6 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-unexisting-test-dir/sonar-project.properties @@ -0,0 +1,7 @@ +sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project + +sonar.sources=sources +sonar.tests=tests diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-unexisting-test-dir/sources/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-unexisting-test-dir/sources/Fake.java new file mode 100644 index 00000000000..e67004defc5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project-with-unexisting-test-dir/sources/Fake.java @@ -0,0 +1 @@ +class Fake {} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project/libs/lib1.txt b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project/libs/lib1.txt new file mode 100644 index 00000000000..81d4e95a0b6 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project/libs/lib1.txt @@ -0,0 +1 @@ +lib1
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project/libs/lib2.txt b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project/libs/lib2.txt new file mode 100644 index 00000000000..7dacac0fd9a --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project/libs/lib2.txt @@ -0,0 +1 @@ +lib2
\ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project/sonar-project.properties b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project/sonar-project.properties new file mode 100644 index 00000000000..69ccd8d1411 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project/sonar-project.properties @@ -0,0 +1,7 @@ +sonar.projectKey=com.foo.project +sonar.projectName=Foo Project +sonar.projectVersion=1.0-SNAPSHOT +sonar.projectDescription=Description of Foo Project + +sonar.sources=sources +sonar.libraries=libs/*.txt diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project/sources/Fake.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project/sources/Fake.java new file mode 100644 index 00000000000..e67004defc5 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/ProjectReactorBuilderTest/simple-project/sources/Fake.java @@ -0,0 +1 @@ +class Fake {} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/report/JSONReportTest/report-without-resolved-issues.json b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/report/JSONReportTest/report-without-resolved-issues.json new file mode 100644 index 00000000000..b5af45efe6c --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/report/JSONReportTest/report-without-resolved-issues.json @@ -0,0 +1,28 @@ +{ + "version": "3.6", + "issues": [], + "components": [ + {"key": "struts"}, + { + "key": "struts-core", + "path": "core" + }, + { + "key": "struts-ui", + "path": "ui" + }, + { + "key": "struts:src/main/java/org/apache/struts/Action.java", + "path": "src/main/java/org/apache/struts/Action.java", + "moduleKey": "struts", + "status": "CHANGED" + }, + { + "key": "struts:src/main/java/org/apache/struts", + "path": "src/main/java/org/apache/struts", + "moduleKey": "struts" + } + ], + "rules": [], + "users": [] +} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/report/JSONReportTest/report.json b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/report/JSONReportTest/report.json new file mode 100644 index 00000000000..a33e06342fa --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/scan/report/JSONReportTest/report.json @@ -0,0 +1,64 @@ +{ + "version": "3.6", + "issues": [ + { + "key": "200", + "component": "struts:src/main/java/org/apache/struts/Action.java", + "line": 1, + "startLine": 1, + "startOffset": 3, + "endLine": 2, + "endOffset": 4, + "message": "There are 2 cycles", + "severity": "MINOR", + "rule": "squid:AvoidCycles", + "status": "OPEN", + "isNew": false, + "assignee": "simon", + "effortToFix": 3.14, + "creationDate": "${json-unit.ignore}" + } + ], + "components": [ + { + "key": "struts" + }, + { + "key": "struts-core", + "path": "core" + }, + { + "key": "struts-ui", + "path": "ui" + }, + { + "key": "struts:src/main/java/org/apache/struts/Action.java", + "path": "src/main/java/org/apache/struts/Action.java", + "moduleKey": "struts", + "status": "CHANGED" + }, + { + "key": "struts:src/main/java/org/apache/struts", + "path": "src/main/java/org/apache/struts", + "moduleKey": "struts" + } + ], + "rules": [ + { + "key": "squid:AvoidCycles", + "rule": "AvoidCycles", + "repository": "squid", + "name": "Avoid Cycles" + } + ], + "users": [ + { + "login": "julien", + "name": "Julien" + }, + { + "login": "simon", + "name": "Simon" + } + ] +} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/source/CodeColorizersTest/Person.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/source/CodeColorizersTest/Person.java new file mode 100644 index 00000000000..c5cc9793730 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/source/CodeColorizersTest/Person.java @@ -0,0 +1,12 @@ +/** + * Doc + */ +public class Person { + + private int first; + + @Deprecated + public void foo(int first, String last, Double middle) { + this.first = first; // First + } +} diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/source/CodeColorizersTest/Person.js b/sonar-scanner-engine/src/test/resources/org/sonar/batch/source/CodeColorizersTest/Person.js new file mode 100644 index 00000000000..fc36e5aa127 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/source/CodeColorizersTest/Person.js @@ -0,0 +1,8 @@ +/** + * Doc + */ +var Person = function(first, last, middle) { + this.first = first; // First + this.middle = ''; + this.last = 1; +}; diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/source/CodeColorizersTest/package.html b/sonar-scanner-engine/src/test/resources/org/sonar/batch/source/CodeColorizersTest/package.html new file mode 100644 index 00000000000..f2d90e627d6 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/source/CodeColorizersTest/package.html @@ -0,0 +1 @@ +<!-- $Header: /cvshome/build/org.osgi.service.log/src/org/osgi/service/log/package.html,v 1.3 2005/08/10 01:43:20 hargrave Exp $ -->
<BODY>
<P>The OSGi Log Service Package. Specification Version 1.3.
<p>Bundles wishing to use this package must list the package
in the Import-Package header of the bundle's manifest.
For example:
<pre>
Import-Package: org.osgi.service.log; version=1.3
</pre>
</BODY>
\ No newline at end of file |