aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-scanner-engine/src/it
diff options
context:
space:
mode:
authorLukasz Jarocki <lukasz.jarocki@sonarsource.com>2023-03-17 10:05:42 +0100
committerLukasz Jarocki <lukasz.jarocki@sonarsource.com>2023-03-17 10:45:58 +0100
commitc9c41f52a93f25286a9d516caa58fcca089ffbc4 (patch)
tree828aee6829652026930cc18879dbc216f7e1b8d3 /sonar-scanner-engine/src/it
parent83e185826c57a1d287d0f622fc50f80017fad944 (diff)
downloadsonarqube-c9c41f52a93f25286a9d516caa58fcca089ffbc4.tar.gz
sonarqube-c9c41f52a93f25286a9d516caa58fcca089ffbc4.zip
SONAR-18679 moved unit test to integration test in sonar-scanner-engine
Diffstat (limited to 'sonar-scanner-engine/src/it')
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java582
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/branch/BranchMediumIT.java163
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/branch/DeprecatedBranchMediumIT.java88
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/coverage/CoverageMediumIT.java368
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/coverage/GenericCoverageMediumIT.java95
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/cpd/CpdMediumIT.java473
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/fs/FileSystemMediumIT.java1282
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/fs/NoLanguagesPluginsMediumIT.java57
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/fs/ProjectBuilderMediumIT.java130
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/highlighting/HighlightingMediumIT.java132
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/ChecksMediumIT.java110
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/ExternalIssuesMediumIT.java184
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/IssuesMediumIT.java452
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/IssuesOnDirMediumIT.java102
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/IssuesOnModuleMediumIT.java73
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/MultilineIssuesMediumIT.java137
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/PreviewMediumIT.java66
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/log/ExceptionHandlingMediumIT.java102
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/log/LogListenerIT.java256
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/measures/MeasuresMediumIT.java182
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/scm/ScmMediumIT.java358
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/symbol/SymbolMediumIT.java105
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/tasks/TasksMediumIT.java75
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/tests/GenericTestExecutionMediumIT.java90
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scm/svn/SvnBlameCommandIT.java391
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scm/svn/SvnTester.java129
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scm/svn/SvnTesterIT.java68
-rw-r--r--sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn-with-merge.zipbin0 -> 29020 bytes
-rw-r--r--sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn.zipbin0 -> 23239 bytes
-rw-r--r--sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn-with-merge.zipbin0 -> 29020 bytes
-rw-r--r--sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn.zipbin0 -> 23239 bytes
-rw-r--r--sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn-with-merge.zipbin0 -> 29020 bytes
-rw-r--r--sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn.zipbin0 -> 23777 bytes
-rw-r--r--sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn-with-merge.zipbin0 -> 37668 bytes
-rw-r--r--sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn.zipbin0 -> 32696 bytes
35 files changed, 6250 insertions, 0 deletions
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java
new file mode 100644
index 00000000000..a7a181a6f43
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java
@@ -0,0 +1,582 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.mediumtest;
+
+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.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Properties;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import javax.annotation.Priority;
+import org.apache.commons.io.FileUtils;
+import org.junit.rules.ExternalResource;
+import org.sonar.api.Plugin;
+import org.sonar.api.SonarEdition;
+import org.sonar.api.SonarProduct;
+import org.sonar.api.SonarQubeSide;
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.batch.rule.LoadedActiveRule;
+import org.sonar.api.impl.server.RulesDefinitionContext;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.rule.RulesDefinition;
+import org.sonar.api.server.rule.RulesDefinition.Repository;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.Version;
+import org.sonar.batch.bootstrapper.Batch;
+import org.sonar.batch.bootstrapper.EnvironmentInformation;
+import org.sonar.batch.bootstrapper.LogOutput;
+import org.sonar.scanner.bootstrap.GlobalAnalysisMode;
+import org.sonar.scanner.cache.AnalysisCacheLoader;
+import org.sonar.scanner.protocol.internal.SensorCacheData;
+import org.sonar.scanner.report.CeTaskReportDataHolder;
+import org.sonar.scanner.repository.FileData;
+import org.sonar.scanner.repository.MetricsRepository;
+import org.sonar.scanner.repository.MetricsRepositoryLoader;
+import org.sonar.scanner.repository.NewCodePeriodLoader;
+import org.sonar.scanner.repository.ProjectRepositories;
+import org.sonar.scanner.repository.ProjectRepositoriesLoader;
+import org.sonar.scanner.repository.QualityProfileLoader;
+import org.sonar.scanner.repository.SingleProjectRepository;
+import org.sonar.scanner.repository.settings.GlobalSettingsLoader;
+import org.sonar.scanner.repository.settings.ProjectSettingsLoader;
+import org.sonar.scanner.rule.ActiveRulesLoader;
+import org.sonar.scanner.rule.RulesLoader;
+import org.sonar.scanner.scan.ScanProperties;
+import org.sonar.scanner.scan.branch.BranchConfiguration;
+import org.sonar.scanner.scan.branch.BranchConfigurationLoader;
+import org.sonar.scanner.scan.branch.BranchType;
+import org.sonar.scanner.scan.branch.ProjectBranches;
+import org.sonarqube.ws.NewCodePeriods;
+import org.sonarqube.ws.Qualityprofiles.SearchWsResponse.QualityProfile;
+import org.sonarqube.ws.Rules.ListResponse.Rule;
+
+import static java.util.Collections.emptySet;
+
+/**
+ * Main utility class for writing scanner medium tests.
+ */
+public class ScannerMediumTester extends ExternalResource {
+
+ private static Path userHome = null;
+ private final Map<String, String> globalProperties = new HashMap<>();
+ private final FakeMetricsRepositoryLoader globalRefProvider = new FakeMetricsRepositoryLoader();
+ private final FakeBranchConfigurationLoader branchConfigurationLoader = new FakeBranchConfigurationLoader();
+ private final FakeBranchConfiguration branchConfiguration = new FakeBranchConfiguration();
+ private final FakeProjectRepositoriesLoader projectRefProvider = new FakeProjectRepositoriesLoader();
+ private final FakePluginInstaller pluginInstaller = new FakePluginInstaller();
+ private final FakeGlobalSettingsLoader globalSettingsLoader = new FakeGlobalSettingsLoader();
+ private final FakeProjectSettingsLoader projectSettingsLoader = new FakeProjectSettingsLoader();
+ private final FakeNewCodePeriodLoader newCodePeriodLoader = new FakeNewCodePeriodLoader();
+ private final FakeAnalysisCacheLoader analysisCacheLoader = new FakeAnalysisCacheLoader();
+ private final FakeRulesLoader rulesLoader = new FakeRulesLoader();
+ private final FakeQualityProfileLoader qualityProfiles = new FakeQualityProfileLoader();
+ private final FakeActiveRulesLoader activeRules = new FakeActiveRulesLoader();
+ private final FakeSonarRuntime sonarRuntime = new FakeSonarRuntime();
+ private final CeTaskReportDataHolder reportMetadataHolder = new CeTaskReportDataHolderExt();
+ private LogOutput logOutput = null;
+
+ private static void createWorkingDirs() throws IOException {
+ destroyWorkingDirs();
+
+ userHome = java.nio.file.Files.createTempDirectory("mediumtest-userHome");
+ }
+
+ private static void destroyWorkingDirs() throws IOException {
+ if (userHome != null) {
+ FileUtils.deleteDirectory(userHome.toFile());
+ userHome = null;
+ }
+ }
+
+ public ScannerMediumTester setLogOutput(LogOutput logOutput) {
+ this.logOutput = logOutput;
+ return this;
+ }
+
+ public ScannerMediumTester registerPlugin(String pluginKey, File location) {
+ return registerPlugin(pluginKey, location, 1L);
+ }
+
+ public ScannerMediumTester registerPlugin(String pluginKey, File location, long lastUpdatedAt) {
+ pluginInstaller.add(pluginKey, location, lastUpdatedAt);
+ return this;
+ }
+
+ public ScannerMediumTester registerPlugin(String pluginKey, Plugin instance) {
+ return registerPlugin(pluginKey, instance, 1L);
+ }
+
+ public ScannerMediumTester registerPlugin(String pluginKey, Plugin instance, long lastUpdatedAt) {
+ pluginInstaller.add(pluginKey, instance, lastUpdatedAt);
+ return this;
+ }
+
+ public ScannerMediumTester registerCoreMetrics() {
+ for (Metric<?> m : CoreMetrics.getMetrics()) {
+ registerMetric(m);
+ }
+ return this;
+ }
+
+ public ScannerMediumTester registerMetric(Metric<?> metric) {
+ globalRefProvider.add(metric);
+ return this;
+ }
+
+ public ScannerMediumTester addQProfile(String language, String name) {
+ qualityProfiles.add(language, name);
+ return this;
+ }
+
+ public ScannerMediumTester addRule(Rule rule) {
+ rulesLoader.addRule(rule);
+ return this;
+ }
+
+ public ScannerMediumTester 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 ScannerMediumTester addRules(RulesDefinition rulesDefinition) {
+ RulesDefinition.Context context = new RulesDefinitionContext();
+ 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 ScannerMediumTester addDefaultQProfile(String language, String name) {
+ addQProfile(language, name);
+ return this;
+ }
+
+ public ScannerMediumTester bootstrapProperties(Map<String, String> props) {
+ globalProperties.putAll(props);
+ return this;
+ }
+
+ public ScannerMediumTester activateRule(LoadedActiveRule activeRule) {
+ activeRules.addActiveRule(activeRule);
+ return this;
+ }
+
+ public ScannerMediumTester addActiveRule(String repositoryKey, String ruleKey, @Nullable String templateRuleKey, String name, @Nullable String severity,
+ @Nullable String internalKey, @Nullable String language) {
+ LoadedActiveRule r = new LoadedActiveRule();
+
+ r.setInternalKey(internalKey);
+ r.setRuleKey(RuleKey.of(repositoryKey, ruleKey));
+ r.setName(name);
+ r.setTemplateRuleKey(templateRuleKey);
+ r.setLanguage(language);
+ r.setSeverity(severity);
+ r.setDeprecatedKeys(emptySet());
+
+ activeRules.addActiveRule(r);
+ return this;
+ }
+
+ public ScannerMediumTester addFileData(String path, FileData fileData) {
+ projectRefProvider.addFileData(path, fileData);
+ return this;
+ }
+
+ public ScannerMediumTester addGlobalServerSettings(String key, String value) {
+ globalSettingsLoader.getGlobalSettings().put(key, value);
+ return this;
+ }
+
+ public ScannerMediumTester addProjectServerSettings(String key, String value) {
+ projectSettingsLoader.getProjectSettings().put(key, value);
+ return this;
+ }
+
+ public ScannerMediumTester setNewCodePeriod(NewCodePeriods.NewCodePeriodType type, String value) {
+ newCodePeriodLoader.set(NewCodePeriods.ShowWSResponse.newBuilder().setType(type).setValue(value).build());
+ return this;
+ }
+
+ @Override
+ protected void before() {
+ try {
+ createWorkingDirs();
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ registerCoreMetrics();
+ globalProperties.put(GlobalAnalysisMode.MEDIUM_TEST_ENABLED, "true");
+ globalProperties.put(ScanProperties.KEEP_REPORT_PROP_KEY, "true");
+ globalProperties.put("sonar.userHome", userHome.toString());
+ }
+
+ @Override
+ protected void after() {
+ try {
+ destroyWorkingDirs();
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ public AnalysisBuilder newAnalysis() {
+ return new AnalysisBuilder(this);
+ }
+
+ public AnalysisBuilder newAnalysis(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);
+ }
+ AnalysisBuilder builder = new AnalysisBuilder(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 AnalysisBuilder {
+ private final Map<String, String> taskProperties = new HashMap<>();
+ private final ScannerMediumTester tester;
+
+ public AnalysisBuilder(ScannerMediumTester tester) {
+ this.tester = tester;
+ }
+
+ public AnalysisResult execute() {
+ AnalysisResult result = new AnalysisResult();
+ Map<String, String> props = new HashMap<>();
+ props.putAll(tester.globalProperties);
+ props.putAll(taskProperties);
+
+ Batch.builder()
+ .setGlobalProperties(props)
+ .setEnableLoggingConfiguration(true)
+ .addComponents(new EnvironmentInformation("mediumTest", "1.0"),
+ tester.pluginInstaller,
+ tester.globalRefProvider,
+ tester.qualityProfiles,
+ tester.rulesLoader,
+ tester.branchConfigurationLoader,
+ tester.projectRefProvider,
+ tester.activeRules,
+ tester.globalSettingsLoader,
+ tester.projectSettingsLoader,
+ tester.newCodePeriodLoader,
+ tester.analysisCacheLoader,
+ tester.sonarRuntime,
+ tester.reportMetadataHolder,
+ result)
+ .setLogOutput(tester.logOutput)
+ .build().execute();
+
+ return result;
+ }
+
+ public AnalysisBuilder properties(Map<String, String> props) {
+ taskProperties.putAll(props);
+ return this;
+ }
+
+ public AnalysisBuilder property(String key, String value) {
+ taskProperties.put(key, value);
+ return this;
+ }
+
+ }
+
+ @Priority(1)
+ private static class FakeRulesLoader implements RulesLoader {
+ private List<Rule> rules = new LinkedList<>();
+
+ public FakeRulesLoader addRule(Rule rule) {
+ rules.add(rule);
+ return this;
+ }
+
+ @Override
+ public List<Rule> load() {
+ return rules;
+ }
+ }
+
+ @Priority(1)
+ 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) {
+ return activeRules;
+ }
+ }
+
+ @Priority(1)
+ private static class FakeMetricsRepositoryLoader implements MetricsRepositoryLoader {
+
+ private int metricId = 1;
+
+ private List<Metric> metrics = new ArrayList<>();
+
+ @Override
+ public MetricsRepository load() {
+ return new MetricsRepository(metrics);
+ }
+
+ public FakeMetricsRepositoryLoader add(Metric<?> metric) {
+ metric.setUuid("metric" + metricId++);
+ metrics.add(metric);
+ metricId++;
+ return this;
+ }
+
+ }
+
+ @Priority(1)
+ private static class FakeProjectRepositoriesLoader implements ProjectRepositoriesLoader {
+ private Map<String, FileData> fileDataMap = new HashMap<>();
+
+ @Override
+ public ProjectRepositories load(String projectKey, @Nullable String branchBase) {
+ return new SingleProjectRepository(fileDataMap);
+ }
+
+ public FakeProjectRepositoriesLoader addFileData(String path, FileData fileData) {
+ fileDataMap.put(path, fileData);
+ return this;
+ }
+
+ }
+
+ @Priority(1)
+ private static class FakeBranchConfiguration implements BranchConfiguration {
+
+ private BranchType branchType = BranchType.BRANCH;
+ private String branchName = null;
+ private String branchTarget = null;
+ private String referenceBranchName = null;
+
+ @Override
+ public BranchType branchType() {
+ return branchType;
+ }
+
+ @CheckForNull
+ @Override
+ public String branchName() {
+ return branchName;
+ }
+
+ @CheckForNull
+ @Override
+ public String targetBranchName() {
+ return branchTarget;
+ }
+
+ @CheckForNull
+ @Override
+ public String referenceBranchName() {
+ return referenceBranchName;
+ }
+
+ @Override
+ public String pullRequestKey() {
+ return "1'";
+ }
+ }
+
+ @Priority(1)
+ private static class FakeSonarRuntime implements SonarRuntime {
+
+ private SonarEdition edition;
+
+ FakeSonarRuntime() {
+ this.edition = SonarEdition.COMMUNITY;
+ }
+
+ @Override
+ public Version getApiVersion() {
+ return Version.create(7, 8);
+ }
+
+ @Override
+ public SonarProduct getProduct() {
+ return SonarProduct.SONARQUBE;
+ }
+
+ @Override
+ public SonarQubeSide getSonarQubeSide() {
+ return SonarQubeSide.SCANNER;
+ }
+
+ @Override
+ public SonarEdition getEdition() {
+ return edition;
+ }
+
+ public void setEdition(SonarEdition edition) {
+ this.edition = edition;
+ }
+ }
+
+ public ScannerMediumTester setBranchType(BranchType branchType) {
+ branchConfiguration.branchType = branchType;
+ return this;
+ }
+
+ public ScannerMediumTester setBranchName(String branchName) {
+ this.branchConfiguration.branchName = branchName;
+ return this;
+ }
+
+ public ScannerMediumTester setBranchTarget(String branchTarget) {
+ this.branchConfiguration.branchTarget = branchTarget;
+ return this;
+ }
+
+ public ScannerMediumTester setReferenceBranchName(String referenceBranchNam) {
+ this.branchConfiguration.referenceBranchName = referenceBranchNam;
+ return this;
+ }
+
+ public ScannerMediumTester setEdition(SonarEdition edition) {
+ this.sonarRuntime.setEdition(edition);
+ return this;
+ }
+
+ @Priority(1)
+ private class FakeBranchConfigurationLoader implements BranchConfigurationLoader {
+ @Override
+ public BranchConfiguration load(Map<String, String> projectSettings, ProjectBranches branches) {
+ return branchConfiguration;
+ }
+ }
+
+ @Priority(1)
+ 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) {
+ return qualityProfiles;
+ }
+ }
+
+ @Priority(1)
+ private static class FakeAnalysisCacheLoader implements AnalysisCacheLoader {
+ @Override
+ public Optional<SensorCacheData> load() {
+ return Optional.empty();
+ }
+ }
+
+ @Priority(1)
+ private static class FakeGlobalSettingsLoader implements GlobalSettingsLoader {
+
+ private Map<String, String> globalSettings = new HashMap<>();
+
+ public Map<String, String> getGlobalSettings() {
+ return globalSettings;
+ }
+
+ @Override
+ public Map<String, String> loadGlobalSettings() {
+ return Collections.unmodifiableMap(globalSettings);
+ }
+ }
+
+ @Priority(1)
+ private static class FakeNewCodePeriodLoader implements NewCodePeriodLoader {
+ private NewCodePeriods.ShowWSResponse response;
+
+ @Override
+ public NewCodePeriods.ShowWSResponse load(String projectKey, String branchName) {
+ return response;
+ }
+
+ public void set(NewCodePeriods.ShowWSResponse response) {
+ this.response = response;
+ }
+ }
+
+ @Priority(1)
+ private static class FakeProjectSettingsLoader implements ProjectSettingsLoader {
+
+ private Map<String, String> projectSettings = new HashMap<>();
+
+ public Map<String, String> getProjectSettings() {
+ return projectSettings;
+ }
+
+ @Override
+ public Map<String, String> loadProjectSettings() {
+ return Collections.unmodifiableMap(projectSettings);
+ }
+ }
+
+ @Priority(1)
+ private static class CeTaskReportDataHolderExt extends CeTaskReportDataHolder {
+
+ }
+
+}
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/branch/BranchMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/branch/BranchMediumIT.java
new file mode 100644
index 00000000000..02f68d9e74b
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/branch/BranchMediumIT.java
@@ -0,0 +1,163 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.mediumtest.branch;
+
+import com.google.common.collect.ImmutableMap;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+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.FileMetadata;
+import org.sonar.api.notifications.AnalysisWarnings;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.scanner.mediumtest.AnalysisResult;
+import org.sonar.scanner.mediumtest.ScannerMediumTester;
+import org.sonar.scanner.protocol.output.ScannerReport;
+import org.sonar.scanner.repository.FileData;
+import org.sonar.scanner.scan.branch.BranchType;
+import org.sonar.xoo.XooPlugin;
+import org.sonar.xoo.rule.XooRulesDefinition;
+import org.sonarqube.ws.NewCodePeriods;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class BranchMediumIT {
+
+ private static final String PROJECT_KEY = "sample";
+ private static final String FILE_PATH = "HelloJava.xoo";
+ private static final String FILE_CONTENT = "xoooo";
+ public static final String ONE_ISSUE_PER_LINE_IS_RESTRICTED_TO_CHANGED_FILES_ONLY = "Sensor One Issue Per Line is restricted to changed files only";
+ private File baseDir;
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public LogTester logTester = new LogTester();
+
+ @Rule
+ public ScannerMediumTester tester = new ScannerMediumTester()
+ .registerPlugin("xoo", new XooPlugin())
+ .addDefaultQProfile("xoo", "Sonar Way")
+ .addRules(new XooRulesDefinition())
+ .addActiveRule("xoo", "OneIssuePerLine", null, "One issue per line", "MAJOR", "OneIssuePerLine.internal", "xoo");
+
+ @Before
+ public void prepare() throws IOException {
+ baseDir = temp.newFolder();
+ Path filepath = baseDir.toPath().resolve(FILE_PATH);
+ Files.write(filepath, FILE_CONTENT.getBytes());
+
+ Path xooUtCoverageFile = baseDir.toPath().resolve(FILE_PATH + ".coverage");
+ FileUtils.write(xooUtCoverageFile.toFile(), "1:2:2:1", StandardCharsets.UTF_8);
+
+ String md5sum = new FileMetadata(mock(AnalysisWarnings.class))
+ .readMetadata(Files.newInputStream(filepath), StandardCharsets.UTF_8, FILE_PATH)
+ .hash();
+ tester.addFileData(FILE_PATH, new FileData(md5sum, "1.1"));
+ tester.setNewCodePeriod(NewCodePeriods.NewCodePeriodType.PREVIOUS_VERSION, "");
+ }
+
+ @Test
+ public void should_not_skip_report_for_unchanged_files_in_pr() {
+ // sanity check, normally report gets generated
+ AnalysisResult result = getResult(tester);
+ final DefaultInputFile file = (DefaultInputFile) result.inputFile(FILE_PATH);
+ assertThat(getResult(tester).getReportComponent(file)).isNotNull();
+ int fileId = file.scannerId();
+ assertThat(result.getReportReader().readChangesets(fileId)).isNotNull();
+ assertThat(result.getReportReader().hasCoverage(fileId)).isTrue();
+ assertThat(result.getReportReader().readFileSource(fileId)).isNotNull();
+
+ // file is not skipped for pull requests (need coverage, duplications coming soon)
+ AnalysisResult result2 = getResult(tester.setBranchType(BranchType.PULL_REQUEST));
+ final DefaultInputFile fileInPr = (DefaultInputFile) result2.inputFile(FILE_PATH);
+ assertThat(result2.getReportComponent(fileInPr)).isNotNull();
+ fileId = fileInPr.scannerId();
+ assertThat(result2.getReportReader().readChangesets(fileId)).isNull();
+ assertThat(result2.getReportReader().hasCoverage(fileId)).isTrue();
+ assertThat(result2.getReportReader().readFileSource(fileId)).isNull();
+ }
+
+ @Test
+ public void shouldSkipSensorForUnchangedFilesOnPr() {
+ AnalysisResult result = getResult(tester
+ .setBranchName("myBranch")
+ .setBranchTarget("main")
+ .setBranchType(BranchType.PULL_REQUEST));
+ final DefaultInputFile file = (DefaultInputFile) result.inputFile(FILE_PATH);
+
+ List<ScannerReport.Issue> issues = result.issuesFor(file);
+ assertThat(issues).isEmpty();
+
+ assertThat(logTester.logs()).contains(ONE_ISSUE_PER_LINE_IS_RESTRICTED_TO_CHANGED_FILES_ONLY);
+ }
+
+ @Test
+ public void shouldNotSkipSensorForUnchangedFilesOnBranch() throws Exception {
+ AnalysisResult result = getResult(tester
+ .setBranchName("myBranch")
+ .setBranchTarget("main")
+ .setBranchType(BranchType.BRANCH));
+ final DefaultInputFile file = (DefaultInputFile) result.inputFile(FILE_PATH);
+
+ List<ScannerReport.Issue> issues = result.issuesFor(file);
+ assertThat(issues).isNotEmpty();
+
+ assertThat(logTester.logs()).doesNotContain(ONE_ISSUE_PER_LINE_IS_RESTRICTED_TO_CHANGED_FILES_ONLY);
+ }
+
+ @Test
+ public void verify_metadata() {
+ String branchName = "feature";
+ String branchTarget = "branch-1.x";
+
+ AnalysisResult result = getResult(tester
+ .setBranchName(branchName)
+ .setBranchTarget(branchTarget)
+ .setReferenceBranchName(branchTarget)
+ .setBranchType(BranchType.BRANCH));
+
+ ScannerReport.Metadata metadata = result.getReportReader().readMetadata();
+ assertThat(metadata.getBranchName()).isEqualTo(branchName);
+ assertThat(metadata.getBranchType()).isEqualTo(ScannerReport.Metadata.BranchType.BRANCH);
+ assertThat(metadata.getReferenceBranchName()).isEqualTo(branchTarget);
+ }
+
+ private AnalysisResult getResult(ScannerMediumTester tester) {
+ return tester
+ .newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", PROJECT_KEY)
+ .put("sonar.scm.provider", "xoo")
+ .build())
+ .execute();
+ }
+}
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/branch/DeprecatedBranchMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/branch/DeprecatedBranchMediumIT.java
new file mode 100644
index 00000000000..a6453f2f12e
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/branch/DeprecatedBranchMediumIT.java
@@ -0,0 +1,88 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.mediumtest.branch;
+
+import com.google.common.collect.ImmutableMap;
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+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.utils.MessageException;
+import org.sonar.scanner.mediumtest.ScannerMediumTester;
+import org.sonar.xoo.XooPlugin;
+import org.sonar.xoo.rule.XooRulesDefinition;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+public class DeprecatedBranchMediumIT {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public ScannerMediumTester tester = new ScannerMediumTester()
+ .registerPlugin("xoo", new XooPlugin())
+ .addRules(new XooRulesDefinition())
+ // active a rule just to be sure that xoo files are published
+ .addActiveRule("xoo", "xoo:OneIssuePerFile", null, "One Issue Per File", null, null, null)
+ .addDefaultQProfile("xoo", "Sonar Way");
+
+ private File baseDir;
+
+ private Map<String, String> commonProps;
+
+ @Before
+ public void prepare() {
+ baseDir = temp.getRoot();
+
+ commonProps = 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();
+ }
+
+ @Test
+ public void scanProjectWithBranch() throws IOException {
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ File xooFile = new File(srcDir, "sample.xoo");
+ FileUtils.write(xooFile, "Sample xoo\ncontent");
+
+ assertThatThrownBy(() -> tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .putAll(commonProps)
+ .put("sonar.branch", "branch")
+ .build())
+ .execute())
+ .isInstanceOf(MessageException.class)
+ .hasMessage("The 'sonar.branch' parameter is no longer supported. You should stop using it. " +
+ "Branch analysis is available in Developer Edition and above. See https://www.sonarsource.com/plans-and-pricing/developer/ for more information.");
+ }
+}
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/coverage/CoverageMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/coverage/CoverageMediumIT.java
new file mode 100644
index 00000000000..08faf0f0f5e
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/coverage/CoverageMediumIT.java
@@ -0,0 +1,368 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.mediumtest.coverage;
+
+import com.google.common.collect.ImmutableMap;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+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.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.scanner.mediumtest.AnalysisResult;
+import org.sonar.scanner.mediumtest.ScannerMediumTester;
+import org.sonar.scanner.protocol.output.ScannerReport;
+import org.sonar.xoo.XooPlugin;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.AssertionsForClassTypes.tuple;
+
+public class CoverageMediumIT {
+
+ @Rule
+ public LogTester logTester = new LogTester();
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public ScannerMediumTester tester = new ScannerMediumTester()
+ .registerPlugin("xoo", new XooPlugin())
+ .addDefaultQProfile("xoo", "Sonar Way");
+
+ @Test
+ public void singleReport() 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}", StandardCharsets.UTF_8);
+ FileUtils.write(xooUtCoverageFile, "2:2:2:1\n3:1", StandardCharsets.UTF_8);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+ InputFile file = result.inputFile("src/sample.xoo");
+ assertThat(result.coverageFor(file, 2).getHits()).isTrue();
+ assertThat(result.coverageFor(file, 2).getConditions()).isEqualTo(2);
+ assertThat(result.coverageFor(file, 2).getCoveredConditions()).isOne();
+ }
+
+ @Test
+ public void twoReports() throws IOException {
+
+ File baseDir = temp.getRoot();
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ File xooFile = new File(srcDir, "sample.xoo");
+ FileUtils.write(xooFile, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8);
+ File xooUtCoverageFile = new File(srcDir, "sample.xoo.coverage");
+ FileUtils.write(xooUtCoverageFile, "2:2:2:2\n4:0", StandardCharsets.UTF_8);
+ File xooItCoverageFile = new File(srcDir, "sample.xoo.itcoverage");
+ FileUtils.write(xooItCoverageFile, "2:0:2:1\n3:1\n5:0", StandardCharsets.UTF_8);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+ InputFile file = result.inputFile("src/sample.xoo");
+ assertThat(result.coverageFor(file, 2).getHits()).isTrue();
+ assertThat(result.coverageFor(file, 2).getConditions()).isEqualTo(2);
+ assertThat(result.coverageFor(file, 2).getCoveredConditions()).isEqualTo(2);
+ assertThat(result.coverageFor(file, 3).getHits()).isTrue();
+ }
+
+ @Test
+ public void exclusionsForSimpleProject() 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}", StandardCharsets.UTF_8);
+ FileUtils.write(xooUtCoverageFile, "2:2:2:1\n3:1", StandardCharsets.UTF_8);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .put("sonar.coverage.exclusions", "**/sample.xoo")
+ .build())
+ .execute();
+
+ InputFile file = result.inputFile("src/sample.xoo");
+ assertThat(result.coverageFor(file, 2)).isNull();
+ }
+
+ @Test
+ public void warn_user_for_outdated_inherited_scanner_side_exclusions_for_multi_module_project() throws IOException {
+
+ File baseDir = temp.getRoot();
+ File baseDirModuleA = new File(baseDir, "moduleA");
+ File baseDirModuleB = new File(baseDir, "moduleB");
+ File srcDirA = new File(baseDirModuleA, "src");
+ srcDirA.mkdirs();
+ File srcDirB = new File(baseDirModuleB, "src");
+ srcDirB.mkdirs();
+
+ File xooFileA = new File(srcDirA, "sampleA.xoo");
+ File xooUtCoverageFileA = new File(srcDirA, "sampleA.xoo.coverage");
+ FileUtils.write(xooFileA, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8);
+ FileUtils.write(xooUtCoverageFileA, "2:2:2:1\n3:1", StandardCharsets.UTF_8);
+
+ File xooFileB = new File(srcDirB, "sampleB.xoo");
+ File xooUtCoverageFileB = new File(srcDirB, "sampleB.xoo.coverage");
+ FileUtils.write(xooFileB, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8);
+ FileUtils.write(xooUtCoverageFileB, "2:2:2:1\n3:1", StandardCharsets.UTF_8);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .put("sonar.modules", "moduleA,moduleB")
+ .put("sonar.coverage.exclusions", "src/sampleA.xoo")
+ .build())
+ .execute();
+
+ InputFile fileA = result.inputFile("moduleA/src/sampleA.xoo");
+ assertThat(result.coverageFor(fileA, 2)).isNull();
+
+ InputFile fileB = result.inputFile("moduleB/src/sampleB.xoo");
+ assertThat(result.coverageFor(fileB, 2)).isNotNull();
+
+ assertThat(logTester.logs(LoggerLevel.WARN)).contains("Specifying module-relative paths at project level in the property 'sonar.coverage.exclusions' is deprecated. " +
+ "To continue matching files like 'moduleA/src/sampleA.xoo', update this property so that patterns refer to project-relative paths.");
+ }
+
+ @Test
+ public void module_level_exclusions_override_parent_for_multi_module_project() throws IOException {
+
+ File baseDir = temp.getRoot();
+ File baseDirModuleA = new File(baseDir, "moduleA");
+ File baseDirModuleB = new File(baseDir, "moduleB");
+ File srcDirA = new File(baseDirModuleA, "src");
+ srcDirA.mkdirs();
+ File srcDirB = new File(baseDirModuleB, "src");
+ srcDirB.mkdirs();
+
+ File xooFileA = new File(srcDirA, "sampleA.xoo");
+ File xooUtCoverageFileA = new File(srcDirA, "sampleA.xoo.coverage");
+ FileUtils.write(xooFileA, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8);
+ FileUtils.write(xooUtCoverageFileA, "2:2:2:1\n3:1", StandardCharsets.UTF_8);
+
+ File xooFileB = new File(srcDirB, "sampleB.xoo");
+ File xooUtCoverageFileB = new File(srcDirB, "sampleB.xoo.coverage");
+ FileUtils.write(xooFileB, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8);
+ FileUtils.write(xooUtCoverageFileB, "2:2:2:1\n3:1", StandardCharsets.UTF_8);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .put("sonar.modules", "moduleA,moduleB")
+ .put("sonar.coverage.exclusions", "**/*.xoo")
+ .put("moduleA.sonar.coverage.exclusions", "**/*.nothing")
+ .build())
+ .execute();
+
+ InputFile fileA = result.inputFile("moduleA/src/sampleA.xoo");
+ assertThat(result.coverageFor(fileA, 2)).isNotNull();
+
+ InputFile fileB = result.inputFile("moduleB/src/sampleB.xoo");
+ assertThat(result.coverageFor(fileB, 2)).isNull();
+ }
+
+ @Test
+ public void warn_user_for_outdated_server_side_exclusions_for_multi_module_project() throws IOException {
+
+ File baseDir = temp.getRoot();
+ File baseDirModuleA = new File(baseDir, "moduleA");
+ File baseDirModuleB = new File(baseDir, "moduleB");
+ File srcDirA = new File(baseDirModuleA, "src");
+ srcDirA.mkdirs();
+ File srcDirB = new File(baseDirModuleB, "src");
+ srcDirB.mkdirs();
+
+ File xooFileA = new File(srcDirA, "sample.xoo");
+ File xooUtCoverageFileA = new File(srcDirA, "sample.xoo.coverage");
+ FileUtils.write(xooFileA, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8);
+ FileUtils.write(xooUtCoverageFileA, "2:2:2:1\n3:1", StandardCharsets.UTF_8);
+
+ File xooFileB = new File(srcDirB, "sample.xoo");
+ File xooUtCoverageFileB = new File(srcDirB, "sample.xoo.coverage");
+ FileUtils.write(xooFileB, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8);
+ FileUtils.write(xooUtCoverageFileB, "2:2:2:1\n3:1", StandardCharsets.UTF_8);
+
+ tester.addProjectServerSettings("sonar.coverage.exclusions", "src/sample.xoo");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .put("sonar.modules", "moduleA,moduleB")
+ .build())
+ .execute();
+
+ InputFile fileA = result.inputFile("moduleA/src/sample.xoo");
+ assertThat(result.coverageFor(fileA, 2)).isNull();
+
+ InputFile fileB = result.inputFile("moduleB/src/sample.xoo");
+ assertThat(result.coverageFor(fileB, 2)).isNull();
+
+ assertThat(logTester.logs(LoggerLevel.WARN)).contains("Specifying module-relative paths at project level in the property 'sonar.coverage.exclusions' is deprecated. " +
+ "To continue matching files like 'moduleA/src/sample.xoo', update this property so that patterns refer to project-relative paths.");
+ }
+
+ @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}", StandardCharsets.UTF_8);
+ FileUtils.write(measuresFile, "executable_lines_data:2=1;3=1;4=0", StandardCharsets.UTF_8);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+ InputFile file = result.inputFile("src/sample.xoo");
+ assertThat(result.coverageFor(file, 1)).isNull();
+
+ assertThat(result.coverageFor(file, 2).getHits()).isFalse();
+ assertThat(result.coverageFor(file, 2).getConditions()).isZero();
+ assertThat(result.coverageFor(file, 2).getCoveredConditions()).isZero();
+
+ assertThat(result.coverageFor(file, 3).getHits()).isFalse();
+ assertThat(result.coverageFor(file, 4)).isNull();
+
+ assertThat(result.allMeasures().get(file.key()))
+ .extracting(ScannerReport.Measure::getMetricKey, m -> m.getStringValue().getValue())
+ .contains(tuple("executable_lines_data", "2=1;3=1;4=0"));
+ }
+
+ // SONAR-11641
+ @Test
+ public void dontFallbackOnExecutableLinesIfNoCoverageSaved() 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");
+ File coverageFile = new File(srcDir, "sample.xoo.coverage");
+ FileUtils.write(xooFile, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8);
+ FileUtils.write(measuresFile, "# The code analyzer disagree with the coverage tool and consider some lines to be executable\nexecutable_lines_data:2=1;3=1;4=0",
+ StandardCharsets.UTF_8);
+ FileUtils.write(coverageFile, "# No lines to cover in this file according to the coverage tool", StandardCharsets.UTF_8);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+ InputFile file = result.inputFile("src/sample.xoo");
+ assertThat(result.coverageFor(file, 1)).isNull();
+ assertThat(result.coverageFor(file, 2)).isNull();
+ assertThat(result.coverageFor(file, 3)).isNull();
+ assertThat(result.coverageFor(file, 4)).isNull();
+ }
+
+ // SONAR-9557
+ @Test
+ public void exclusionsAndForceToZeroOnModules() throws IOException {
+
+ File baseDir = temp.getRoot();
+ File srcDir = new File(baseDir, "module1/src");
+ srcDir.mkdir();
+
+ File xooFile1 = new File(srcDir, "sample1.xoo");
+ File measuresFile1 = new File(srcDir, "sample1.xoo.measures");
+ FileUtils.write(xooFile1, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8);
+ FileUtils.write(measuresFile1, "executable_lines_data:2=1;3=1;4=0", StandardCharsets.UTF_8);
+
+ File xooFile2 = new File(srcDir, "sample2.xoo");
+ File measuresFile2 = new File(srcDir, "sample2.xoo.measures");
+ FileUtils.write(xooFile2, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8);
+ FileUtils.write(measuresFile2, "executable_lines_data:2=1;3=1;4=0", StandardCharsets.UTF_8);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.modules", "module1")
+ .put("sonar.sources", "src")
+ .put("sonar.coverage.exclusions", "**/sample2.xoo")
+ .build())
+ .execute();
+
+ InputFile file1 = result.inputFile("module1/src/sample1.xoo");
+ assertThat(result.coverageFor(file1, 1)).isNull();
+
+ assertThat(result.coverageFor(file1, 2).getHits()).isFalse();
+ assertThat(result.coverageFor(file1, 2).getConditions()).isZero();
+ assertThat(result.coverageFor(file1, 2).getCoveredConditions()).isZero();
+
+ assertThat(result.coverageFor(file1, 3).getHits()).isFalse();
+ assertThat(result.coverageFor(file1, 4)).isNull();
+
+ InputFile file2 = result.inputFile("module1/src/sample2.xoo");
+ assertThat(result.coverageFor(file2, 1)).isNull();
+ assertThat(result.coverageFor(file2, 2)).isNull();
+ assertThat(result.coverageFor(file2, 3)).isNull();
+ assertThat(result.coverageFor(file2, 4)).isNull();
+
+ }
+
+}
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/coverage/GenericCoverageMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/coverage/GenericCoverageMediumIT.java
new file mode 100644
index 00000000000..1f16e7241d7
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/coverage/GenericCoverageMediumIT.java
@@ -0,0 +1,95 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.mediumtest.coverage;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.scanner.mediumtest.AnalysisResult;
+import org.sonar.scanner.mediumtest.ScannerMediumTester;
+import org.sonar.xoo.XooPlugin;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class GenericCoverageMediumIT {
+ private final List<String> logs = new ArrayList<>();
+
+ @Rule
+ public ScannerMediumTester tester = new ScannerMediumTester()
+ .registerPlugin("xoo", new XooPlugin())
+ .addDefaultQProfile("xoo", "Sonar Way");
+
+ @Test
+ public void singleReport() {
+
+ File projectDir = new File("test-resources/mediumtest/xoo/sample-generic-coverage");
+
+ AnalysisResult result = tester
+ .setLogOutput((msg, level) -> logs.add(msg))
+ .newAnalysis(new File(projectDir, "sonar-project.properties"))
+ .property("sonar.coverageReportPaths", "coverage.xml")
+ .execute();
+
+ InputFile noConditions = result.inputFile("xources/hello/NoConditions.xoo");
+ assertThat(result.coverageFor(noConditions, 6).getHits()).isTrue();
+ assertThat(result.coverageFor(noConditions, 6).getConditions()).isZero();
+ assertThat(result.coverageFor(noConditions, 6).getCoveredConditions()).isZero();
+
+ assertThat(result.coverageFor(noConditions, 7).getHits()).isFalse();
+
+ InputFile withConditions = result.inputFile("xources/hello/WithConditions.xoo");
+ assertThat(result.coverageFor(withConditions, 3).getHits()).isTrue();
+ assertThat(result.coverageFor(withConditions, 3).getConditions()).isEqualTo(2);
+ assertThat(result.coverageFor(withConditions, 3).getCoveredConditions()).isOne();
+
+ assertThat(logs).noneMatch(l -> l.contains("Please use 'sonar.coverageReportPaths'"));
+
+ }
+
+ @Test
+ public void twoReports() {
+
+ File projectDir = new File("test-resources/mediumtest/xoo/sample-generic-coverage");
+
+ AnalysisResult result = tester
+ .setLogOutput((msg, level) -> logs.add(msg))
+ .newAnalysis(new File(projectDir, "sonar-project.properties"))
+ .property("sonar.coverageReportPaths", "coverage.xml,coverage2.xml")
+ .execute();
+
+ InputFile noConditions = result.inputFile("xources/hello/NoConditions.xoo");
+ assertThat(result.coverageFor(noConditions, 6).getHits()).isTrue();
+ assertThat(result.coverageFor(noConditions, 6).getConditions()).isZero();
+ assertThat(result.coverageFor(noConditions, 6).getCoveredConditions()).isZero();
+
+ assertThat(result.coverageFor(noConditions, 7).getHits()).isTrue();
+
+ InputFile withConditions = result.inputFile("xources/hello/WithConditions.xoo");
+ assertThat(result.coverageFor(withConditions, 3).getHits()).isTrue();
+ assertThat(result.coverageFor(withConditions, 3).getConditions()).isEqualTo(2);
+ assertThat(result.coverageFor(withConditions, 3).getCoveredConditions()).isEqualTo(2);
+
+ assertThat(logs).noneMatch(l -> l.contains("Please use 'sonar.coverageReportPaths'"));
+ }
+
+}
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/cpd/CpdMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/cpd/CpdMediumIT.java
new file mode 100644
index 00000000000..0975bb74a74
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/cpd/CpdMediumIT.java
@@ -0,0 +1,473 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.mediumtest.cpd;
+
+import com.google.common.collect.ImmutableMap;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+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.InputFile;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.scanner.mediumtest.AnalysisResult;
+import org.sonar.scanner.mediumtest.ScannerMediumTester;
+import org.sonar.scanner.protocol.output.ScannerReport;
+import org.sonar.xoo.XooPlugin;
+import org.sonar.xoo.rule.XooRulesDefinition;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class CpdMediumIT {
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public LogTester logTester = new LogTester();
+
+ private File baseDir;
+
+ @Rule
+ public ScannerMediumTester tester = new ScannerMediumTester()
+ .registerPlugin("xoo", new XooPlugin())
+ .addDefaultQProfile("xoo", "Sonar Way")
+ .addRules(new XooRulesDefinition())
+ // active a rule just to be sure that xoo files are published
+ .addActiveRule("xoo", "xoo:OneIssuePerFile", null, "One Issue Per File", null, null, null);
+
+ private ImmutableMap.Builder<String, String> builder;
+
+ @Before
+ public void prepare() {
+ 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");
+ }
+
+ @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);
+
+ AnalysisResult result = tester.newAnalysis().properties(builder.build()).execute();
+
+ assertThat(result.inputFiles()).hasSize(2);
+
+ InputFile inputFile1 = result.inputFile("module1/sample1.xoo");
+ InputFile inputFile2 = result.inputFile("module2/sample2.xoo");
+
+ // One clone group on each file
+ List<ScannerReport.Duplication> duplicationGroupsFile1 = result.duplicationsFor(inputFile1);
+ assertThat(duplicationGroupsFile1).hasSize(1);
+
+ ScannerReport.Duplication cloneGroupFile1 = duplicationGroupsFile1.get(0);
+ assertThat(cloneGroupFile1.getOriginPosition().getStartLine()).isOne();
+ assertThat(cloneGroupFile1.getOriginPosition().getEndLine()).isEqualTo(17);
+ assertThat(cloneGroupFile1.getDuplicateList()).hasSize(1);
+ assertThat(cloneGroupFile1.getDuplicate(0).getOtherFileRef()).isEqualTo(result.getReportComponent(inputFile2).getRef());
+
+ List<ScannerReport.Duplication> duplicationGroupsFile2 = result.duplicationsFor(inputFile2);
+ assertThat(duplicationGroupsFile2).hasSize(1);
+
+ ScannerReport.Duplication cloneGroupFile2 = duplicationGroupsFile2.get(0);
+ assertThat(cloneGroupFile2.getOriginPosition().getStartLine()).isOne();
+ assertThat(cloneGroupFile2.getOriginPosition().getEndLine()).isEqualTo(17);
+ assertThat(cloneGroupFile2.getDuplicateList()).hasSize(1);
+ assertThat(cloneGroupFile2.getDuplicate(0).getOtherFileRef()).isEqualTo(result.getReportComponent(inputFile1).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, StandardCharsets.UTF_8);
+
+ File xooFile2 = new File(srcDir, "sample2.xoo");
+ FileUtils.write(xooFile2, duplicatedStuff, StandardCharsets.UTF_8);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .put("sonar.cpd.xoo.minimumTokens", "10")
+ .put("sonar.verbose", "true")
+ .build())
+ .execute();
+
+ 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<ScannerReport.Duplication> duplicationGroupsFile1 = result.duplicationsFor(inputFile1);
+ assertThat(duplicationGroupsFile1).hasSize(1);
+
+ ScannerReport.Duplication cloneGroupFile1 = duplicationGroupsFile1.get(0);
+ assertThat(cloneGroupFile1.getOriginPosition().getStartLine()).isOne();
+ assertThat(cloneGroupFile1.getOriginPosition().getEndLine()).isEqualTo(17);
+ assertThat(cloneGroupFile1.getDuplicateList()).hasSize(1);
+ assertThat(cloneGroupFile1.getDuplicate(0).getOtherFileRef()).isEqualTo(result.getReportComponent(inputFile2).getRef());
+
+ List<ScannerReport.Duplication> duplicationGroupsFile2 = result.duplicationsFor(inputFile2);
+ assertThat(duplicationGroupsFile2).hasSize(1);
+
+ ScannerReport.Duplication cloneGroupFile2 = duplicationGroupsFile2.get(0);
+ assertThat(cloneGroupFile2.getOriginPosition().getStartLine()).isOne();
+ assertThat(cloneGroupFile2.getOriginPosition().getEndLine()).isEqualTo(17);
+ assertThat(cloneGroupFile2.getDuplicateList()).hasSize(1);
+ assertThat(cloneGroupFile2.getDuplicate(0).getOtherFileRef()).isEqualTo(result.getReportComponent(inputFile1).getRef());
+
+ assertThat(result.duplicationBlocksFor(inputFile1)).isEmpty();
+ }
+
+ @Test
+ public void testFilesWithoutBlocks() throws IOException {
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ String file1 = "Sample xoo\ncontent\n"
+ + "foo\nbar\ntoto\ntiti\n"
+ + "foo\nbar\ntoto\ntiti\n"
+ + "bar\ntoto\ntiti\n"
+ + "foo\nbar\ntoto\ntiti";
+
+ String file2 = "string\n";
+
+ File xooFile1 = new File(srcDir, "sample1.xoo");
+ FileUtils.write(xooFile1, file1, StandardCharsets.UTF_8);
+
+ File xooFile2 = new File(srcDir, "sample2.xoo");
+ FileUtils.write(xooFile2, file2, StandardCharsets.UTF_8);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .put("sonar.cpd.xoo.minimumTokens", "10")
+ .put("sonar.verbose", "true")
+ .build())
+ .execute();
+
+ assertThat(result.inputFiles()).hasSize(2);
+
+ assertThat(logTester.logs()).contains("Not enough content in 'src/sample2.xoo' to have CPD blocks, it will not be part of the duplication detection");
+ assertThat(logTester.logs()).contains("CPD Executor 1 file had no CPD blocks");
+
+ }
+
+ @Test
+ public void testExclusions() 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);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .put("sonar.cpd.xoo.minimumTokens", "10")
+ .put("sonar.cpd.exclusions", "src/sample1.xoo")
+ .build())
+ .execute();
+
+ assertThat(result.inputFiles()).hasSize(2);
+
+ InputFile inputFile1 = result.inputFile("src/sample1.xoo");
+ InputFile inputFile2 = result.inputFile("src/sample2.xoo");
+
+ List<ScannerReport.Duplication> duplicationGroupsFile1 = result.duplicationsFor(inputFile1);
+ assertThat(duplicationGroupsFile1).isEmpty();
+
+ List<ScannerReport.Duplication> duplicationGroupsFile2 = result.duplicationsFor(inputFile2);
+ assertThat(duplicationGroupsFile2).isEmpty();
+ }
+
+ @Test
+ public void cross_module_duplication() throws IOException {
+
+ 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 baseDir = temp.getRoot();
+ File baseDirModuleA = new File(baseDir, "moduleA");
+ File baseDirModuleB = new File(baseDir, "moduleB");
+ File srcDirA = new File(baseDirModuleA, "src");
+ srcDirA.mkdirs();
+ File srcDirB = new File(baseDirModuleB, "src");
+ srcDirB.mkdirs();
+
+ File xooFileA = new File(srcDirA, "sampleA.xoo");
+ FileUtils.write(xooFileA, duplicatedStuff, StandardCharsets.UTF_8);
+
+ File xooFileB = new File(srcDirB, "sampleB.xoo");
+ FileUtils.write(xooFileB, duplicatedStuff, StandardCharsets.UTF_8);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .put("sonar.modules", "moduleA,moduleB")
+ .put("sonar.cpd.xoo.minimumTokens", "10")
+ .build())
+ .execute();
+
+ InputFile inputFile1 = result.inputFile("moduleA/src/sampleA.xoo");
+ InputFile inputFile2 = result.inputFile("moduleB/src/sampleB.xoo");
+
+ List<ScannerReport.Duplication> duplicationGroupsFile1 = result.duplicationsFor(inputFile1);
+ assertThat(duplicationGroupsFile1).isNotEmpty();
+
+ List<ScannerReport.Duplication> duplicationGroupsFile2 = result.duplicationsFor(inputFile2);
+ assertThat(duplicationGroupsFile2).isNotEmpty();
+ }
+
+ @Test
+ public void warn_user_for_outdated_inherited_scanner_side_exclusions_for_multi_module_project() throws IOException {
+
+ 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 baseDir = temp.getRoot();
+ File baseDirModuleA = new File(baseDir, "moduleA");
+ File baseDirModuleB = new File(baseDir, "moduleB");
+ File srcDirA = new File(baseDirModuleA, "src");
+ srcDirA.mkdirs();
+ File srcDirB = new File(baseDirModuleB, "src");
+ srcDirB.mkdirs();
+
+ File xooFileA = new File(srcDirA, "sampleA.xoo");
+ FileUtils.write(xooFileA, duplicatedStuff, StandardCharsets.UTF_8);
+
+ File xooFileB = new File(srcDirB, "sampleB.xoo");
+ FileUtils.write(xooFileB, duplicatedStuff, StandardCharsets.UTF_8);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .put("sonar.modules", "moduleA,moduleB")
+ .put("sonar.cpd.xoo.minimumTokens", "10")
+ .put("sonar.cpd.exclusions", "src/sampleA.xoo")
+ .build())
+ .execute();
+
+ InputFile inputFile1 = result.inputFile("moduleA/src/sampleA.xoo");
+ InputFile inputFile2 = result.inputFile("moduleB/src/sampleB.xoo");
+
+ List<ScannerReport.Duplication> duplicationGroupsFile1 = result.duplicationsFor(inputFile1);
+ assertThat(duplicationGroupsFile1).isEmpty();
+
+ List<ScannerReport.Duplication> duplicationGroupsFile2 = result.duplicationsFor(inputFile2);
+ assertThat(duplicationGroupsFile2).isEmpty();
+
+ assertThat(logTester.logs(LoggerLevel.WARN)).contains("Specifying module-relative paths at project level in the property 'sonar.cpd.exclusions' is deprecated. " +
+ "To continue matching files like 'moduleA/src/sampleA.xoo', update this property so that patterns refer to project-relative paths.");
+ }
+
+ @Test
+ public void module_level_exclusions_override_parent_for_multi_module_project() throws IOException {
+
+ 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 baseDir = temp.getRoot();
+ File baseDirModuleA = new File(baseDir, "moduleA");
+ File baseDirModuleB = new File(baseDir, "moduleB");
+ File srcDirA = new File(baseDirModuleA, "src");
+ srcDirA.mkdirs();
+ File srcDirB = new File(baseDirModuleB, "src");
+ srcDirB.mkdirs();
+
+ File xooFileA = new File(srcDirA, "sampleA.xoo");
+ FileUtils.write(xooFileA, duplicatedStuff, StandardCharsets.UTF_8);
+
+ File xooFileB = new File(srcDirB, "sampleB.xoo");
+ FileUtils.write(xooFileB, duplicatedStuff, StandardCharsets.UTF_8);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .put("sonar.modules", "moduleA,moduleB")
+ .put("sonar.cpd.xoo.minimumTokens", "10")
+ .put("sonar.cpd.exclusions", "**/*")
+ .put("moduleA.sonar.cpd.exclusions", "**/*.nothing")
+ .put("moduleB.sonar.cpd.exclusions", "**/*.nothing")
+ .build())
+ .execute();
+
+ InputFile inputFile1 = result.inputFile("moduleA/src/sampleA.xoo");
+ InputFile inputFile2 = result.inputFile("moduleB/src/sampleB.xoo");
+
+ List<ScannerReport.Duplication> duplicationGroupsFile1 = result.duplicationsFor(inputFile1);
+ assertThat(duplicationGroupsFile1).isNotEmpty();
+
+ List<ScannerReport.Duplication> duplicationGroupsFile2 = result.duplicationsFor(inputFile2);
+ assertThat(duplicationGroupsFile2).isNotEmpty();
+ }
+
+ @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);
+
+ AnalysisResult result = tester.newAnalysis()
+ .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())
+ .execute();
+
+ InputFile inputFile1 = result.inputFile("src/sample1.xoo");
+
+ List<ScannerReport.CpdTextBlock> duplicationBlocks = result.duplicationBlocksFor(inputFile1);
+ assertThat(duplicationBlocks).hasSize(3);
+ assertThat(duplicationBlocks.get(0).getStartLine()).isOne();
+ assertThat(duplicationBlocks.get(0).getEndLine()).isEqualTo(5);
+ assertThat(duplicationBlocks.get(0).getStartTokenIndex()).isOne();
+ 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();
+ }
+
+ @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);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .put("sonar.cpd.xoo.minimumTokens", "2")
+ .put("sonar.cpd.xoo.minimumLines", "2")
+ .put("sonar.verbose", "true")
+ .build())
+ .execute();
+
+ InputFile inputFile = result.inputFile("src/sample.xoo");
+ // One clone group
+ List<ScannerReport.Duplication> duplicationGroups = result.duplicationsFor(inputFile);
+ assertThat(duplicationGroups).hasSize(1);
+
+ ScannerReport.Duplication cloneGroup = duplicationGroups.get(0);
+ assertThat(cloneGroup.getOriginPosition().getStartLine()).isOne();
+ 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/it/java/org/sonar/scanner/mediumtest/fs/FileSystemMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/fs/FileSystemMediumIT.java
new file mode 100644
index 00000000000..9c1c55338b3
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/fs/FileSystemMediumIT.java
@@ -0,0 +1,1282 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.mediumtest.fs;
+
+import com.google.common.collect.ImmutableMap;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Random;
+import java.util.stream.Collectors;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.SystemUtils;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.SonarEdition;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.utils.MessageException;
+import org.sonar.api.utils.PathUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.scanner.mediumtest.AnalysisResult;
+import org.sonar.scanner.mediumtest.ScannerMediumTester;
+import org.sonar.scanner.mediumtest.ScannerMediumTester.AnalysisBuilder;
+import org.sonar.xoo.XooPlugin;
+import org.sonar.xoo.global.DeprecatedGlobalSensor;
+import org.sonar.xoo.global.GlobalProjectSensor;
+import org.sonar.xoo.rule.XooRulesDefinition;
+
+import static java.lang.String.format;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+public class FileSystemMediumIT {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public LogTester logTester = new LogTester();
+
+ @Rule
+ public ScannerMediumTester tester = new ScannerMediumTester()
+ .setEdition(SonarEdition.COMMUNITY)
+ .registerPlugin("xoo", new XooPlugin())
+ .addDefaultQProfile("xoo", "Sonar Way")
+ .addDefaultQProfile("xoo2", "Sonar Way");
+
+ private File baseDir;
+ private ImmutableMap.Builder<String, String> builder;
+
+ @Before
+ public void prepare() throws IOException {
+ baseDir = temp.newFolder().getCanonicalFile();
+
+ builder = ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project");
+ }
+
+ @Test
+ public void scan_project_without_name() throws IOException {
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ writeFile(srcDir, "sample.xoo", "Sample xoo\ncontent");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+ int ref = result.getReportReader().readMetadata().getRootComponentRef();
+ assertThat(result.getReportReader().readComponent(ref).getName()).isEmpty();
+ assertThat(result.inputFiles()).hasSize(1);
+
+ DefaultInputFile file = (DefaultInputFile) result.inputFile("src/sample.xoo");
+ assertThat(file.type()).isEqualTo(InputFile.Type.MAIN);
+ assertThat(file.relativePath()).isEqualTo("src/sample.xoo");
+ assertThat(file.language()).isEqualTo("xoo");
+
+ // file was published, since language matched xoo
+ assertThat(file.isPublished()).isTrue();
+ assertThat(result.getReportComponent(file.scannerId())).isNotNull();
+ }
+
+ @Test
+ public void log_branch_name_and_type() {
+ builder.put("sonar.branch.name", "my-branch");
+ File srcDir = new File(baseDir, "src");
+ assertThat(srcDir.mkdir()).isTrue();
+
+ // Using sonar.branch.name when the branch plugin is not installed is an error.
+ assertThatThrownBy(() -> tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .build())
+ .execute())
+ .isInstanceOf(MessageException.class);
+ }
+
+ @Test
+ public void only_generate_metadata_if_needed() throws IOException {
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ writeFile(srcDir, "sample.xoo", "Sample xoo\ncontent");
+ writeFile(srcDir, "sample.java", "Sample xoo\ncontent");
+
+ logTester.setLevel(LoggerLevel.DEBUG);
+
+ tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+ assertThat(logTester.logs()).contains("2 files indexed");
+ assertThat(logTester.logs()).contains("'src/sample.xoo' generated metadata with charset 'UTF-8'");
+ assertThat(String.join("\n", logTester.logs())).doesNotContain("'src/sample.java' generated metadata");
+ }
+
+ @Test
+ public void preload_file_metadata() throws IOException {
+ builder.put("sonar.preloadFileMetadata", "true");
+
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ writeFile(srcDir, "sample.xoo", "Sample xoo\ncontent");
+ writeFile(srcDir, "sample.java", "Sample xoo\ncontent");
+
+ logTester.setLevel(LoggerLevel.DEBUG);
+
+ tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+ assertThat(logTester.logs()).contains("2 files indexed");
+ assertThat(logTester.logs()).contains("'src/sample.xoo' generated metadata with charset 'UTF-8'");
+ assertThat(logTester.logs()).contains("'src/sample.java' generated metadata with charset 'UTF-8'");
+ }
+
+ @Test
+ public void dont_publish_files_without_detected_language() throws IOException {
+ Path mainDir = baseDir.toPath().resolve("src").resolve("main");
+ Files.createDirectories(mainDir);
+
+ Path testDir = baseDir.toPath().resolve("src").resolve("test");
+ Files.createDirectories(testDir);
+
+ writeFile(testDir.toFile(), "sample.java", "Sample xoo\ncontent");
+ writeFile(mainDir.toFile(), "sample.xoo", "Sample xoo\ncontent");
+ writeFile(mainDir.toFile(), "sample.java", "Sample xoo\ncontent");
+
+ logTester.setLevel(LoggerLevel.DEBUG);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src/main")
+ .put("sonar.tests", "src/test")
+ .build())
+ .execute();
+
+ assertThat(logTester.logs()).containsAnyOf("'src/main/sample.java' indexed with no language", "'src\\main\\sample.java' indexed with no language");
+ assertThat(logTester.logs()).contains("3 files indexed");
+ assertThat(logTester.logs()).contains("'src/main/sample.xoo' generated metadata with charset 'UTF-8'");
+ assertThat(logTester.logs()).doesNotContain("'src/main/sample.java' generated metadata", "'src\\main\\sample.java' generated metadata");
+ assertThat(logTester.logs()).doesNotContain("'src/test/sample.java' generated metadata", "'src\\test\\sample.java' generated metadata");
+ DefaultInputFile javaInputFile = (DefaultInputFile) result.inputFile("src/main/sample.java");
+
+ assertThatThrownBy(() -> result.getReportComponent(javaInputFile))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessageContaining("Unable to find report for component");
+ }
+
+ @Test
+ public void create_issue_on_any_file() throws IOException {
+ tester
+ .addRules(new XooRulesDefinition())
+ .addActiveRule("xoo", "OneIssuePerUnknownFile", null, "OneIssuePerUnknownFile", "MAJOR", null, "xoo");
+
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ writeFile(srcDir, "sample.unknown", "Sample xoo\ncontent");
+
+ logTester.setLevel(LoggerLevel.DEBUG);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+ assertThat(logTester.logs()).contains("1 file indexed");
+ assertThat(logTester.logs()).contains("'src" + File.separator + "sample.unknown' indexed with no language");
+ assertThat(logTester.logs()).contains("'src/sample.unknown' generated metadata with charset 'UTF-8'");
+ DefaultInputFile inputFile = (DefaultInputFile) result.inputFile("src/sample.unknown");
+ assertThat(result.getReportComponent(inputFile)).isNotNull();
+ }
+
+ @Test
+ public void lazyIssueExclusion() throws IOException {
+ tester
+ .addRules(new XooRulesDefinition())
+ .addActiveRule("xoo", "OneIssuePerFile", null, "OneIssuePerFile", "MAJOR", null, "xoo");
+
+ builder.put("sonar.issue.ignore.allfile", "1")
+ .put("sonar.issue.ignore.allfile.1.fileRegexp", "pattern");
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ writeFile(srcDir, "sample.xoo", "Sample xoo\ncontent");
+
+ File unknownFile = new File(srcDir, "myfile.binary");
+ byte[] b = new byte[512];
+ new Random().nextBytes(b);
+ FileUtils.writeByteArrayToFile(unknownFile, b);
+
+ logTester.setLevel(LoggerLevel.DEBUG);
+
+ tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+ assertThat(logTester.logs()).containsOnlyOnce("'src" + File.separator + "myfile.binary' indexed with no language");
+ assertThat(logTester.logs()).doesNotContain("Evaluate issue exclusions for 'src/myfile.binary'");
+ assertThat(logTester.logs()).containsOnlyOnce("Evaluate issue exclusions for 'src/sample.xoo'");
+ }
+
+ @Test
+ public void preloadIssueExclusions() throws IOException {
+ builder.put("sonar.issue.ignore.allfile", "1")
+ .put("sonar.issue.ignore.allfile.1.fileRegexp", "pattern")
+ .put("sonar.preloadFileMetadata", "true");
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ writeFile(srcDir, "sample.xoo", "Sample xoo\npattern");
+ writeFile(srcDir, "myfile.binary", "some text");
+
+ logTester.setLevel(LoggerLevel.DEBUG);
+
+ tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+ assertThat(logTester.logs()).containsSequence("Evaluate issue exclusions for 'src/sample.xoo'",
+ " - Exclusion pattern 'pattern': all issues in this file will be ignored.");
+ }
+
+ @Test
+ public void publishFilesWithIssues() throws IOException {
+ tester
+ .addRules(new XooRulesDefinition())
+ .addActiveRule("xoo", "OneIssueOnDirPerFile", null, "OneIssueOnDirPerFile", "MAJOR", null, "xoo");
+
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ writeFile(srcDir, "sample.xoo", "Sample xoo\ncontent");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+ DefaultInputFile file = (DefaultInputFile) result.inputFile("src/sample.xoo");
+
+ assertThat(file.isPublished()).isTrue();
+ assertThat(result.getReportComponent(file)).isNotNull();
+ }
+
+ @Test
+ public void scanProjectWithSourceDir() throws IOException {
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ writeFile(srcDir, "sample.xoo", "Sample xoo\ncontent");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+ assertThat(result.inputFiles()).hasSize(1);
+ assertThat(result.inputFile("src/sample.xoo").type()).isEqualTo(InputFile.Type.MAIN);
+ assertThat(result.inputFile("src/sample.xoo").relativePath()).isEqualTo("src/sample.xoo");
+ }
+
+ @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));
+ }
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+ assertThat(result.inputFiles()).hasSize(100);
+ }
+
+ @Test
+ public void scanProjectWithTestDir() throws IOException {
+ File test = new File(baseDir, "test");
+ test.mkdir();
+
+ writeFile(test, "sampleTest.xoo", "Sample test xoo\ncontent");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "")
+ .put("sonar.tests", "test")
+ .build())
+ .execute();
+
+ 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();
+
+ writeFile(srcDir, "sample.xoo", "Sample xoo\ncontent");
+ writeFile(baseDir, "another.xoo", "Sample xoo 2\ncontent");
+
+ File testDir = new File(baseDir, "test");
+ testDir.mkdir();
+
+ writeFile(baseDir, "sampleTest2.xoo", "Sample test xoo\ncontent");
+ writeFile(testDir, "sampleTest.xoo", "Sample test xoo 2\ncontent");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src,another.xoo")
+ .put("sonar.tests", "test,sampleTest2.xoo")
+ .build())
+ .execute();
+
+ assertThat(result.inputFiles()).hasSize(4);
+ }
+
+ @Test
+ public void fileInclusionsExclusions() throws IOException {
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ writeFile(srcDir, "sample.xoo", "Sample xoo\ncontent");
+ writeFile(baseDir, "another.xoo", "Sample xoo 2\ncontent");
+
+ File testDir = new File(baseDir, "test");
+ testDir.mkdir();
+
+ writeFile(baseDir, "sampleTest2.xoo", "Sample test xoo\ncontent");
+ writeFile(testDir, "sampleTest.xoo", "Sample test xoo 2\ncontent");
+
+ AnalysisResult result = tester.newAnalysis()
+ .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())
+ .execute();
+
+ assertThat(result.inputFiles()).hasSize(2);
+ }
+
+ @Test
+ public void ignoreFilesWhenGreaterThanDefinedSize() throws IOException {
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ File fileGreaterThanLimit = writeFile(srcDir, "sample.xoo", 1024 * 1024 + 1);
+ writeFile(srcDir, "another.xoo", "Sample xoo 2\ncontent");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ // set limit to 1MB
+ .put("sonar.filesize.limit", "1")
+ .build())
+ .execute();
+
+ assertThat(result.inputFiles()).hasSize(1);
+ assertThat(logTester.logs())
+ .contains(format("File '%s' is bigger than 1MB and as consequence is removed from the analysis scope.", fileGreaterThanLimit.getAbsolutePath()));
+ }
+
+ @Test
+ public void analysisFailsSymbolicLinkWithoutTargetIsInTheFolder() throws IOException {
+ assumeFalse(SystemUtils.IS_OS_WINDOWS);
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ File target = writeFile(srcDir, "target.xoo", 1024 * 1024 + 1);
+ Path link = Paths.get(srcDir.getPath(), "target_link.xoo");
+ Files.createSymbolicLink(link, target.toPath());
+ Files.delete(target.toPath());
+
+ AnalysisBuilder analysis = tester.newAnalysis()
+ .properties(builder.build());
+
+ assertThatThrownBy(analysis::execute)
+ .isExactlyInstanceOf(IllegalStateException.class)
+ .hasMessageEndingWith(format("Unable to read file %s", link));
+ }
+
+ @Test
+ public void test_inclusions_on_multi_modules() throws IOException {
+ File baseDir = temp.getRoot();
+ File baseDirModuleA = new File(baseDir, "moduleA");
+ File baseDirModuleB = new File(baseDir, "moduleB");
+ File srcDirA = new File(baseDirModuleA, "tests");
+ srcDirA.mkdirs();
+ File srcDirB = new File(baseDirModuleB, "tests");
+ srcDirB.mkdirs();
+
+ writeFile(srcDirA, "sampleTestA.xoo", "Sample xoo\ncontent");
+ writeFile(srcDirB, "sampleTestB.xoo", "Sample xoo\ncontent");
+
+ final ImmutableMap.Builder<String, String> builder = ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "")
+ .put("sonar.tests", "tests")
+ .put("sonar.modules", "moduleA,moduleB");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder.build())
+ .execute();
+
+ assertThat(result.inputFiles()).hasSize(2);
+
+ InputFile fileA = result.inputFile("moduleA/tests/sampleTestA.xoo");
+ assertThat(fileA).isNotNull();
+
+ InputFile fileB = result.inputFile("moduleB/tests/sampleTestB.xoo");
+ assertThat(fileB).isNotNull();
+
+ result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.test.inclusions", "moduleA/tests/**")
+ .build())
+ .execute();
+
+ assertThat(result.inputFiles()).hasSize(1);
+
+ fileA = result.inputFile("moduleA/tests/sampleTestA.xoo");
+ assertThat(fileA).isNotNull();
+
+ fileB = result.inputFile("moduleB/tests/sampleTestB.xoo");
+ assertThat(fileB).isNull();
+ }
+
+ @Test
+ public void test_module_level_inclusions_override_parent_on_multi_modules() throws IOException {
+ File baseDir = temp.getRoot();
+ File baseDirModuleA = new File(baseDir, "moduleA");
+ File baseDirModuleB = new File(baseDir, "moduleB");
+ File srcDirA = new File(baseDirModuleA, "src");
+ srcDirA.mkdirs();
+ File srcDirB = new File(baseDirModuleB, "src");
+ srcDirB.mkdirs();
+
+ writeFile(srcDirA, "sampleA.xoo", "Sample xoo\ncontent");
+ writeFile(srcDirB, "sampleB.xoo", "Sample xoo\ncontent");
+
+ final ImmutableMap.Builder<String, String> builder = ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .put("sonar.modules", "moduleA,moduleB")
+ .put("sonar.inclusions", "**/*.php");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder.build())
+ .execute();
+
+ assertThat(result.inputFiles()).isEmpty();
+
+ result = tester.newAnalysis()
+ .properties(builder
+ .put("moduleA.sonar.inclusions", "**/*.xoo")
+ .build())
+ .execute();
+
+ assertThat(result.inputFiles()).hasSize(1);
+
+ InputFile fileA = result.inputFile("moduleA/src/sampleA.xoo");
+ assertThat(fileA).isNotNull();
+ }
+
+ @Test
+ public void warn_user_for_outdated_scanner_side_inherited_exclusions_for_multi_module_project() throws IOException {
+ File baseDir = temp.getRoot();
+ File baseDirModuleA = new File(baseDir, "moduleA");
+ File baseDirModuleB = new File(baseDir, "moduleB");
+ File srcDirA = new File(baseDirModuleA, "src");
+ srcDirA.mkdirs();
+ File srcDirB = new File(baseDirModuleB, "src");
+ srcDirB.mkdirs();
+
+ writeFile(srcDirA, "sample.xoo", "Sample xoo\ncontent");
+ writeFile(srcDirB, "sample.xoo", "Sample xoo\ncontent");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .put("sonar.modules", "moduleA,moduleB")
+ .put("sonar.exclusions", "src/sample.xoo")
+ .build())
+ .execute();
+
+ InputFile fileA = result.inputFile("moduleA/src/sample.xoo");
+ assertThat(fileA).isNull();
+
+ InputFile fileB = result.inputFile("moduleB/src/sample.xoo");
+ assertThat(fileB).isNull();
+
+ assertThat(logTester.logs(LoggerLevel.WARN))
+ .contains("Specifying module-relative paths at project level in the property 'sonar.exclusions' is deprecated. " +
+ "To continue matching files like 'moduleA/src/sample.xoo', update this property so that patterns refer to project-relative paths.");
+ }
+
+ @Test
+ public void support_global_server_side_exclusions_for_multi_module_project() throws IOException {
+ File baseDir = temp.getRoot();
+ File baseDirModuleA = new File(baseDir, "moduleA");
+ File baseDirModuleB = new File(baseDir, "moduleB");
+ File srcDirA = new File(baseDirModuleA, "src");
+ srcDirA.mkdirs();
+ File srcDirB = new File(baseDirModuleB, "src");
+ srcDirB.mkdirs();
+
+ writeFile(srcDirA, "sample.xoo", "Sample xoo\ncontent");
+ writeFile(srcDirB, "sample.xoo", "Sample xoo\ncontent");
+
+ tester.addGlobalServerSettings(CoreProperties.PROJECT_EXCLUSIONS_PROPERTY, "**/*.xoo");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .put("sonar.modules", "moduleA,moduleB")
+ .build())
+ .execute();
+
+ InputFile fileA = result.inputFile("moduleA/src/sample.xoo");
+ assertThat(fileA).isNull();
+
+ InputFile fileB = result.inputFile("moduleB/src/sample.xoo");
+ assertThat(fileB).isNull();
+ }
+
+ @Test
+ public void support_global_server_side_global_exclusions_for_multi_module_project() throws IOException {
+ File baseDir = temp.getRoot();
+ File baseDirModuleA = new File(baseDir, "moduleA");
+ File baseDirModuleB = new File(baseDir, "moduleB");
+ File srcDirA = new File(baseDirModuleA, "src");
+ srcDirA.mkdirs();
+ File srcDirB = new File(baseDirModuleB, "src");
+ srcDirB.mkdirs();
+
+ writeFile(srcDirA, "sample.xoo", "Sample xoo\ncontent");
+ writeFile(srcDirB, "sample.xoo", "Sample xoo\ncontent");
+
+ tester.addGlobalServerSettings(CoreProperties.GLOBAL_EXCLUSIONS_PROPERTY, "**/*.xoo");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .put("sonar.modules", "moduleA,moduleB")
+ .build())
+ .execute();
+
+ InputFile fileA = result.inputFile("moduleA/src/sample.xoo");
+ assertThat(fileA).isNull();
+
+ InputFile fileB = result.inputFile("moduleB/src/sample.xoo");
+ assertThat(fileB).isNull();
+ }
+
+ @Test
+ public void warn_user_for_outdated_server_side_inherited_exclusions_for_multi_module_project() throws IOException {
+ File baseDir = temp.getRoot();
+ File baseDirModuleA = new File(baseDir, "moduleA");
+ File baseDirModuleB = new File(baseDir, "moduleB");
+ File srcDirA = new File(baseDirModuleA, "src");
+ srcDirA.mkdirs();
+ File srcDirB = new File(baseDirModuleB, "src");
+ srcDirB.mkdirs();
+
+ writeFile(srcDirA, "sample.xoo", "Sample xoo\ncontent");
+ writeFile(srcDirB, "sample.xoo", "Sample xoo\ncontent");
+
+ tester.addProjectServerSettings("sonar.exclusions", "src/sample.xoo");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .put("sonar.modules", "moduleA,moduleB")
+ .build())
+ .execute();
+
+ InputFile fileA = result.inputFile("moduleA/src/sample.xoo");
+ assertThat(fileA).isNull();
+
+ InputFile fileB = result.inputFile("moduleB/src/sample.xoo");
+ assertThat(fileB).isNull();
+
+ assertThat(logTester.logs(LoggerLevel.WARN))
+ .contains("Specifying module-relative paths at project level in the property 'sonar.exclusions' is deprecated. " +
+ "To continue matching files like 'moduleA/src/sample.xoo', update this property so that patterns refer to project-relative paths.");
+ }
+
+ @Test
+ public void failForDuplicateInputFile() throws IOException {
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ writeFile(srcDir, "sample.xoo", "Sample xoo\ncontent");
+
+ assertThatThrownBy(() -> tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src,src/sample.xoo")
+ .build())
+ .execute())
+ .isInstanceOf(MessageException.class)
+ .hasMessage("File src/sample.xoo can't be indexed twice. Please check that inclusion/exclusion patterns produce disjoint sets for main and test files");
+ }
+
+ // SONAR-9574
+ @Test
+ public void failForDuplicateInputFileInDifferentModules() throws IOException {
+ File srcDir = new File(baseDir, "module1/src");
+ srcDir.mkdir();
+
+ writeFile(srcDir, "sample.xoo", "Sample xoo\ncontent");
+
+ assertThatThrownBy(() -> tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "module1/src")
+ .put("sonar.modules", "module1")
+ .put("module1.sonar.sources", "src")
+ .build())
+ .execute())
+ .isInstanceOf(MessageException.class)
+ .hasMessage("File module1/src/sample.xoo can't be indexed twice. Please check that inclusion/exclusion patterns produce disjoint sets for main and test files");
+ }
+
+ // SONAR-5330
+ @Test
+ public void scanProjectWithSourceSymlink() {
+ assumeTrue(!System2.INSTANCE.isOsWindows());
+ File projectDir = new File("test-resources/mediumtest/xoo/sample-with-symlink");
+ AnalysisResult result = tester
+ .newAnalysis(new File(projectDir, "sonar-project.properties"))
+ .property("sonar.exclusions", "**/*.xoo.measures,**/*.xoo.scm")
+ .property("sonar.test.exclusions", "**/*.xoo.measures,**/*.xoo.scm")
+ .property("sonar.scm.exclusions.disabled", "true")
+ .execute();
+
+ 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() {
+ // To please the quality gate, don't use assumeTrue, or the test will be reported as skipped
+ File projectDir = new File("test-resources/mediumtest/xoo/sample");
+ AnalysisBuilder analysis = tester
+ .newAnalysis(new File(projectDir, "sonar-project.properties"))
+ .property("sonar.sources", "XOURCES")
+ .property("sonar.tests", "TESTX")
+ .property("sonar.scm.exclusions.disabled", "true");
+
+ if (System2.INSTANCE.isOsWindows()) { // Windows is file path case-insensitive
+ AnalysisResult result = analysis.execute();
+
+ assertThat(result.inputFiles()).hasSize(8);
+ assertThat(result.inputFiles()).extractingResultOf("relativePath").containsOnly(
+ "testx/ClassOneTest.xoo.measures",
+ "xources/hello/helloscala.xoo.measures",
+ "xources/hello/HelloJava.xoo.measures",
+ "testx/ClassOneTest.xoo",
+ "xources/hello/HelloJava.xoo.scm",
+ "xources/hello/helloscala.xoo",
+ "testx/ClassOneTest.xoo.scm",
+ "xources/hello/HelloJava.xoo");
+ } else if (System2.INSTANCE.isOsMac()) {
+ AnalysisResult result = analysis.execute();
+
+ assertThat(result.inputFiles()).hasSize(8);
+ assertThat(result.inputFiles()).extractingResultOf("relativePath").containsOnly(
+ "TESTX/ClassOneTest.xoo.measures",
+ "XOURCES/hello/helloscala.xoo.measures",
+ "XOURCES/hello/HelloJava.xoo.measures",
+ "TESTX/ClassOneTest.xoo",
+ "XOURCES/hello/HelloJava.xoo.scm",
+ "XOURCES/hello/helloscala.xoo",
+ "TESTX/ClassOneTest.xoo.scm",
+ "XOURCES/hello/HelloJava.xoo");
+ } else {
+ assertThatThrownBy(() -> analysis.execute())
+ .isInstanceOf(MessageException.class)
+ .hasMessageContaining("The folder 'TESTX' does not exist for 'sample'");
+ }
+ }
+
+ @Test
+ public void indexAnyFile() throws IOException {
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ writeFile(srcDir, "sample.xoo", "Sample xoo\ncontent");
+ writeFile(srcDir, "sample.other", "Sample other\ncontent");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+ 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("test-resources/mediumtest/xoo/multi-modules-sample");
+ AnalysisResult result = tester
+ .newAnalysis(new File(projectDir, "sonar-project.properties"))
+ .execute();
+
+ assertThat(result.inputFiles()).hasSize(4);
+ }
+
+ @Test
+ public void deprecated_global_sensor_should_see_project_relative_paths() {
+ File projectDir = new File("test-resources/mediumtest/xoo/multi-modules-sample");
+ AnalysisResult result = tester
+ .newAnalysis(new File(projectDir, "sonar-project.properties"))
+ .property(DeprecatedGlobalSensor.ENABLE_PROP, "true")
+ .execute();
+
+ assertThat(result.inputFiles()).hasSize(4);
+ assertThat(logTester.logs(LoggerLevel.INFO)).contains(
+ "Deprecated Global Sensor: module_a/module_a1/src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo",
+ "Deprecated Global Sensor: module_a/module_a2/src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo",
+ "Deprecated Global Sensor: module_b/module_b1/src/main/xoo/com/sonar/it/samples/modules/b1/HelloB1.xoo",
+ "Deprecated Global Sensor: module_b/module_b2/src/main/xoo/com/sonar/it/samples/modules/b2/HelloB2.xoo");
+ }
+
+ @Test
+ public void global_sensor_should_see_project_relative_paths() {
+ File projectDir = new File("test-resources/mediumtest/xoo/multi-modules-sample");
+ AnalysisResult result = tester
+ .newAnalysis(new File(projectDir, "sonar-project.properties"))
+ .property(GlobalProjectSensor.ENABLE_PROP, "true")
+ .execute();
+
+ assertThat(result.inputFiles()).hasSize(4);
+ assertThat(logTester.logs(LoggerLevel.INFO)).contains(
+ "Global Sensor: module_a/module_a1/src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo",
+ "Global Sensor: module_a/module_a2/src/main/xoo/com/sonar/it/samples/modules/a2/HelloA2.xoo",
+ "Global Sensor: module_b/module_b1/src/main/xoo/com/sonar/it/samples/modules/b1/HelloB1.xoo",
+ "Global Sensor: module_b/module_b2/src/main/xoo/com/sonar/it/samples/modules/b2/HelloB2.xoo");
+ }
+
+ @Test
+ public void scan_project_with_comma_in_source_path() throws IOException {
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ writeFile(srcDir, "sample,1.xoo", "Sample xoo\ncontent");
+ writeFile(baseDir, "another,2.xoo", "Sample xoo 2\ncontent");
+
+ File testDir = new File(baseDir, "test");
+ testDir.mkdir();
+
+ writeFile(testDir, "sampleTest,1.xoo", "Sample test xoo\ncontent");
+ writeFile(baseDir, "sampleTest,2.xoo", "Sample test xoo 2\ncontent");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src,\"another,2.xoo\"")
+ .put("sonar.tests", "\"test\",\"sampleTest,2.xoo\"")
+ .build())
+ .execute();
+
+ assertThat(result.inputFiles()).hasSize(4);
+ }
+
+ @Test
+ public void language_without_publishAllFiles_should_not_auto_publish_files() throws IOException {
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ writeFile(srcDir, "sample.xoo3", "Sample xoo\ncontent");
+ writeFile(srcDir, "sample2.xoo3", "Sample xoo 2\ncontent");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+ assertThat(result.inputFiles())
+ .extracting(InputFile::filename, InputFile::language, f -> ((DefaultInputFile) f).isPublished())
+ .containsOnly(tuple("sample.xoo3", "xoo3", false), tuple("sample2.xoo3", "xoo3", false));
+ assertThat(result.getReportReader().readComponent(result.getReportReader().readMetadata().getRootComponentRef()).getChildRefCount()).isZero();
+ }
+
+ @Test
+ public void two_languages_with_same_extension() throws IOException {
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ writeFile(srcDir, "sample.xoo", "Sample xoo\ncontent");
+ writeFile(srcDir, "sample.xoo2", "Sample xoo 2\ncontent");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+ assertThat(result.inputFiles()).hasSize(2);
+
+ AnalysisBuilder analysisBuilder = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.lang.patterns.xoo2", "**/*.xoo")
+ .build());
+
+ assertThatThrownBy(analysisBuilder::execute)
+ .isInstanceOf(MessageException.class)
+ .hasMessage(
+ "Language of file 'src" + File.separator
+ + "sample.xoo' can not be decided as the file matches patterns of both sonar.lang.patterns.xoo : **/*.xoo and sonar.lang.patterns.xoo2 : **/*.xoo");
+
+ // SONAR-9561
+ result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.exclusions", "**/sample.xoo")
+ .build())
+ .execute();
+
+ assertThat(result.inputFiles()).hasSize(1);
+ }
+
+ @Test
+ public void log_all_exclusions_properties_per_modules() throws IOException {
+ File baseDir = temp.getRoot();
+ File baseDirModuleA = new File(baseDir, "moduleA");
+ File baseDirModuleB = new File(baseDir, "moduleB");
+ File srcDirA = new File(baseDirModuleA, "src");
+ srcDirA.mkdirs();
+ File srcDirB = new File(baseDirModuleB, "src");
+ srcDirB.mkdirs();
+
+ writeFile(srcDirA, "sample.xoo", "Sample xoo\ncontent");
+ writeFile(srcDirB, "sample.xoo", "Sample xoo\ncontent");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .put("sonar.modules", "moduleA,moduleB")
+ .put("sonar.inclusions", "**/global.inclusions")
+ .put("sonar.test.inclusions", "**/global.test.inclusions")
+ .put("sonar.exclusions", "**/global.exclusions")
+ .put("sonar.test.exclusions", "**/global.test.exclusions")
+ .put("sonar.coverage.exclusions", "**/coverage.exclusions")
+ .put("sonar.cpd.exclusions", "**/cpd.exclusions")
+ .build())
+ .execute();
+
+ assertThat(logTester.logs(LoggerLevel.INFO))
+ .containsSequence("Project configuration:",
+ " Included sources: **/global.inclusions",
+ " Excluded sources: **/global.exclusions, **/global.test.inclusions",
+ " Included tests: **/global.test.inclusions",
+ " Excluded tests: **/global.test.exclusions",
+ " Excluded sources for coverage: **/coverage.exclusions",
+ " Excluded sources for duplication: **/cpd.exclusions",
+ "Indexing files of module 'moduleA'",
+ " Base dir: " + baseDirModuleA.toPath().toRealPath(LinkOption.NOFOLLOW_LINKS),
+ " Source paths: src",
+ " Included sources: **/global.inclusions",
+ " Excluded sources: **/global.exclusions, **/global.test.inclusions",
+ " Included tests: **/global.test.inclusions",
+ " Excluded tests: **/global.test.exclusions",
+ " Excluded sources for coverage: **/coverage.exclusions",
+ " Excluded sources for duplication: **/cpd.exclusions",
+ "Indexing files of module 'moduleB'",
+ " Base dir: " + baseDirModuleB.toPath().toRealPath(LinkOption.NOFOLLOW_LINKS),
+ " Source paths: src",
+ " Included sources: **/global.inclusions",
+ " Excluded sources: **/global.exclusions, **/global.test.inclusions",
+ " Included tests: **/global.test.inclusions",
+ " Excluded tests: **/global.test.exclusions",
+ " Excluded sources for coverage: **/coverage.exclusions",
+ " Excluded sources for duplication: **/cpd.exclusions",
+ "Indexing files of module 'com.foo.project'",
+ " Base dir: " + baseDir.toPath().toRealPath(LinkOption.NOFOLLOW_LINKS),
+ " Included sources: **/global.inclusions",
+ " Excluded sources: **/global.exclusions, **/global.test.inclusions",
+ " Included tests: **/global.test.inclusions",
+ " Excluded tests: **/global.test.exclusions",
+ " Excluded sources for coverage: **/coverage.exclusions",
+ " Excluded sources for duplication: **/cpd.exclusions");
+ }
+
+ @Test
+ public void ignore_files_outside_project_basedir() throws IOException {
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ writeFile(srcDir, "sample1.xoo", "Sample xoo\ncontent");
+
+ File outsideBaseDir = temp.newFolder().getCanonicalFile();
+ File xooFile2 = writeFile(outsideBaseDir, "another.xoo", "Sample xoo 2\ncontent");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src," + PathUtils.canonicalPath(xooFile2))
+ .build())
+ .execute();
+
+ assertThat(result.inputFiles()).hasSize(1);
+ assertThat(logTester.logs(LoggerLevel.WARN)).contains("File '" + xooFile2.getAbsolutePath() + "' is ignored. It is not located in project basedir '" + baseDir + "'.");
+ }
+
+ @Test
+ public void dont_log_warn_about_files_out_of_basedir_if_they_arent_included() throws IOException {
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ writeFile(srcDir, "sample1.xoo", "Sample xoo\ncontent");
+
+ File outsideBaseDir = temp.newFolder().getCanonicalFile();
+ File xooFile2 = new File(outsideBaseDir, "another.xoo");
+ FileUtils.write(xooFile2, "Sample xoo 2\ncontent", StandardCharsets.UTF_8);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src," + PathUtils.canonicalPath(xooFile2))
+ .put("sonar.inclusions", "**/sample1.xoo")
+ .build())
+ .execute();
+
+ assertThat(result.inputFiles()).hasSize(1);
+ assertThat(logTester.logs(LoggerLevel.WARN)).doesNotContain("File '" + xooFile2.getAbsolutePath() + "' is ignored. It is not located in project basedir '" + baseDir + "'.");
+ }
+
+ @Test
+ public void ignore_files_outside_module_basedir() throws IOException {
+ File moduleA = new File(baseDir, "moduleA");
+ moduleA.mkdir();
+
+ writeFile(moduleA, "src/sampleA.xoo", "Sample xoo\ncontent");
+ File xooFile2 = writeFile(baseDir, "another.xoo", "Sample xoo 2\ncontent");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.modules", "moduleA")
+ .put("moduleA.sonar.sources", "src," + PathUtils.canonicalPath(xooFile2))
+ .build())
+ .execute();
+
+ assertThat(result.inputFiles()).hasSize(1);
+ assertThat(logTester.logs(LoggerLevel.WARN))
+ .contains("File '" + xooFile2.getAbsolutePath() + "' is ignored. It is not located in module basedir '" + new File(baseDir, "moduleA") + "'.");
+ }
+
+ @Test
+ public void exclusion_based_on_scm_info() {
+ File projectDir = new File("test-resources/mediumtest/xoo/sample-with-ignored-file");
+ AnalysisResult result = tester
+ .newAnalysis(new File(projectDir, "sonar-project.properties"))
+ .property("sonar.exclusions", "**/*.xoo.ignore")
+ .property("sonar.test.exclusions", "**/*.xoo.ignore")
+ .execute();
+
+ assertThat(result.inputFile("xources/hello/ClassTwo.xoo")).isNull();
+ assertThat(result.inputFile("testx/ClassTwoTest.xoo")).isNull();
+
+ assertThat(result.inputFile("xources/hello/ClassOne.xoo")).isNotNull();
+ assertThat(result.inputFile("testx/ClassOneTest.xoo")).isNotNull();
+ }
+
+ @Test
+ public void no_exclusion_when_scm_exclusions_is_disabled() {
+ File projectDir = new File("test-resources/mediumtest/xoo/sample-with-ignored-file");
+ AnalysisResult result = tester
+ .newAnalysis(new File(projectDir, "sonar-project.properties"))
+ .property("sonar.scm.exclusions.disabled", "true")
+ .property("sonar.exclusions", "**/*.xoo.ignore")
+ .property("sonar.test.exclusions", "**/*.xoo.ignore")
+ .execute();
+
+ assertThat(result.inputFiles()).hasSize(4);
+
+ assertThat(result.inputFile("xources/hello/ClassTwo.xoo")).isNotNull();
+ assertThat(result.inputFile("testx/ClassTwoTest.xoo")).isNotNull();
+ assertThat(result.inputFile("xources/hello/ClassOne.xoo")).isNotNull();
+ assertThat(result.inputFile("testx/ClassOneTest.xoo")).isNotNull();
+ }
+
+ @Test
+ public void index_basedir_by_default() throws IOException {
+ writeFile(baseDir, "sample.xoo", "Sample xoo\ncontent");
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ .build())
+ .execute();
+
+ assertThat(logTester.logs()).contains("1 file indexed");
+ assertThat(result.inputFile("sample.xoo")).isNotNull();
+ }
+
+ @Test
+ public void givenExclusionEndingWithOneWildcardWhenAnalysedThenOnlyDirectChildrenFilesShouldBeExcluded() throws IOException {
+ // src/src.xoo
+ File srcDir = createDir(baseDir, "src", true);
+ writeFile(srcDir, "src.xoo", "Sample xoo 2\ncontent");
+
+ // src/srcSubDir/srcSub.xoo
+ File srcSubDir = createDir(srcDir, "srcSubDir", true);
+ writeFile(srcSubDir, "srcSub.xoo", "Sample xoo\ncontent");
+
+ // src/srcSubDir/srcSubSubDir/subSubSrc.xoo
+ File srcSubSubDir = createDir(srcSubDir, "srcSubSubDir", true);
+ writeFile(srcSubSubDir, "subSubSrc.xoo", "Sample xoo\ncontent");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .put("sonar.exclusions", "src/srcSubDir/*")
+ .build())
+ .execute();
+
+ assertAnalysedFiles(result, "src/src.xoo", "src/srcSubDir/srcSubSubDir/subSubSrc.xoo");
+ }
+
+ @Test
+ public void givenPathsWithoutReadPermissionWhenAllChildrenAreExcludedThenScannerShouldSkipIt() throws IOException {
+ // src/src.xoo
+ File srcDir = createDir(baseDir, "src", true);
+ writeFile(srcDir, "src.xoo", "Sample xoo 2\ncontent");
+
+ // src/srcSubDir/srcSub.xoo
+ File srcSubDir = createDir(srcDir, "srcSubDir", false);
+ writeFile(srcSubDir, "srcSub.xoo", "Sample xoo\ncontent");
+
+ // src/srcSubDir2/srcSub2.xoo
+ File srcSubDir2 = createDir(srcDir, "srcSubDir2", true);
+ boolean fileNotReadable = writeFile(srcSubDir2, "srcSub2.xoo", "Sample 2 xoo\ncontent").setReadable(false);
+ assumeTrue(fileNotReadable);
+
+ // src/srcSubDir2/srcSubSubDir2/srcSubSub2.xoo
+ File srcSubSubDir2 = createDir(srcSubDir2, "srcSubSubDir2", false);
+ writeFile(srcSubSubDir2, "srcSubSub2.xoo", "Sample xoo\ncontent");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .put("sonar.exclusions", "src/srcSubDir/**/*,src/srcSubDir2/**/*")
+ .build())
+ .execute();
+
+ assertAnalysedFiles(result, "src/src.xoo");
+ assertThat(logTester.logs()).contains("1 file ignored because of inclusion/exclusion patterns");
+ }
+
+ @Test
+ public void givenFileWithoutAccessWhenChildrenAreExcludedThenThenScanShouldFail() throws IOException {
+ // src/src.xoo
+ File srcDir = createDir(baseDir, "src", true);
+ boolean fileNotReadable = writeFile(srcDir, "src.xoo", "Sample xoo\ncontent").setReadable(false);
+ assumeTrue(fileNotReadable);
+
+ AnalysisBuilder result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .put("sonar.exclusions", "src/src.xoo/**/*") // incorrect pattern, but still the scan should fail if src.xoo is not accessible
+ .build());
+
+ assertThatThrownBy(result::execute)
+ .isExactlyInstanceOf(IllegalStateException.class)
+ .hasMessageStartingWith(format("java.lang.IllegalStateException: Unable to read file"));
+ }
+
+ @Test
+ public void givenDirectoryWithoutReadPermissionWhenIncludedThenScanShouldFail() throws IOException {
+ // src/src.xoo
+ File srcDir = createDir(baseDir, "src", true);
+ writeFile(srcDir, "src.xoo", "Sample xoo 2\ncontent");
+
+ // src/srcSubDir/srcSub.xoo
+ File srcSubDir = createDir(srcDir, "srcSubDir", false);
+ writeFile(srcSubDir, "srcSub.xoo", "Sample xoo\ncontent");
+
+ AnalysisBuilder result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .put("sonar.exclusions", "src/srcSubDir/*") // srcSubDir should not be excluded unless all children are excluded (src/srcSubDir/**/*)
+ .build());
+
+ assertThatThrownBy(result::execute)
+ .isExactlyInstanceOf(IllegalStateException.class)
+ .hasMessageEndingWith(format("Failed to index files"));
+ }
+
+ @Test
+ public void givenDirectoryWhenAllChildrenAreExcludedThenSkippedFilesShouldBeReported() throws IOException {
+ // src/src.xoo
+ File srcDir = createDir(baseDir, "src", true);
+ writeFile(srcDir, "src.xoo", "Sample xoo 2\ncontent");
+
+ // src/srcSubDir/srcSub.xoo
+ File srcSubDir = createDir(srcDir, "srcSubDir", true);
+ writeFile(srcSubDir, "srcSub.xoo", "Sample xoo\ncontent");
+
+ // src/srcSubDir2/srcSub2.xoo
+ File srcSubDir2 = createDir(srcDir, "srcSubDir2", true);
+ boolean fileNotReadable = writeFile(srcSubDir2, "srcSub2.xoo", "Sample 2 xoo\ncontent").setReadable(false);
+ assumeTrue(fileNotReadable);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .put("sonar.exclusions", "src/srcSubDir/**/*,src/srcSubDir2/*")
+ .build())
+ .execute();
+
+ assertAnalysedFiles(result, "src/src.xoo");
+ assertThat(logTester.logs()).contains("2 files ignored because of inclusion/exclusion patterns");
+ }
+
+ @Ignore("Fails until we can pattern match inclusions to directories, not only files.")
+ @Test
+ public void givenDirectoryWithoutReadPermissionWhenNotIncludedThenScanShouldSkipIt() throws IOException {
+ // src/src.xoo
+ File srcDir = createDir(baseDir, "src", true);
+ writeFile(srcDir, "src.xoo", "Sample xoo 2\ncontent");
+
+ // src/srcSubDir/srcSub.xoo
+ File srcSubDir = createDir(srcDir, "srcSubDir", true);
+ writeFile(srcSubDir, "srcSub.xoo", "Sample xoo\ncontent");
+
+ // src/srcSubDir2/srcSub2.xoo
+ File srcSubDir2 = createDir(srcDir, "srcSubDir2", false);
+ writeFile(srcSubDir2, "srcSub2.xoo", "Sample xoo\ncontent");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .put("sonar.inclusions", "src/srcSubDir/**/*")
+ .build())
+ .execute();
+
+ assertAnalysedFiles(result, "src/srcSubDir/srcSub.xoo");
+ }
+
+ @Test
+ public void givenDirectoryWithoutReadPermissionUnderSourcesWhenAnalysedThenShouldFail() throws IOException {
+ // src/src.xoo
+ File srcDir = createDir(baseDir, "src", true);
+ writeFile(srcDir, "src.xoo", "Sample xoo 2\ncontent");
+
+ // src/srcSubDir/srcSub.xoo
+ File srcSubDir = createDir(srcDir, "srcSubDir", false);
+ writeFile(srcSubDir, "srcSub.xoo", "Sample xoo\ncontent");
+
+ AnalysisBuilder result = tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .build());
+
+ assertThatThrownBy(result::execute)
+ .isExactlyInstanceOf(IllegalStateException.class)
+ .hasMessageEndingWith(format("Failed to index files"));
+ }
+
+ private static void assertAnalysedFiles(AnalysisResult result, String... files) {
+ assertThat(result.inputFiles().stream().map(InputFile::toString).collect(Collectors.toList())).contains(files);
+ }
+
+ private File createDir(File parentDir, String name, boolean isReadable) {
+ File dir = new File(parentDir, name);
+ dir.mkdir();
+ boolean fileSystemOperationSucceded = dir.setReadable(isReadable);
+ assumeTrue(fileSystemOperationSucceded); //On windows + java there is no reliable way to play with readable/not readable flag
+ return dir;
+ }
+
+ private File writeFile(File parent, String name, String content) throws IOException {
+ File file = new File(parent, name);
+ FileUtils.write(file, content, StandardCharsets.UTF_8);
+ return file;
+ }
+
+ private File writeFile(File parent, String name, long size) throws IOException {
+ File file = new File(parent, name);
+ RandomAccessFile raf = new RandomAccessFile(file, "rw");
+ raf.setLength(size);
+ raf.close();
+ return file;
+ }
+
+}
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/fs/NoLanguagesPluginsMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/fs/NoLanguagesPluginsMediumIT.java
new file mode 100644
index 00000000000..a32056a4cb7
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/fs/NoLanguagesPluginsMediumIT.java
@@ -0,0 +1,57 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.mediumtest.fs;
+
+import java.io.File;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.filefilter.FileFilterUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.scanner.mediumtest.ScannerMediumTester;
+import org.springframework.beans.factory.BeanCreationException;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+public class NoLanguagesPluginsMediumIT {
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public ScannerMediumTester tester = new ScannerMediumTester();
+
+ @Test
+ public void testNoLanguagePluginsInstalled() throws Exception {
+ File projectDir = copyProject("test-resources/mediumtest/xoo/sample");
+
+ assertThatThrownBy(() -> tester
+ .newAnalysis(new File(projectDir, "sonar-project.properties"))
+ .execute())
+ .isInstanceOf(BeanCreationException.class)
+ .hasRootCauseMessage("No language plugins are installed.");
+ }
+
+ private File copyProject(String path) throws Exception {
+ File projectDir = temp.newFolder();
+ File originalProjectDir = new File(path);
+ FileUtils.copyDirectory(originalProjectDir, projectDir, FileFilterUtils.notFileFilter(FileFilterUtils.nameFileFilter(".sonar")));
+ return projectDir;
+ }
+}
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/fs/ProjectBuilderMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/fs/ProjectBuilderMediumIT.java
new file mode 100644
index 00000000000..d7e75c25173
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/fs/ProjectBuilderMediumIT.java
@@ -0,0 +1,130 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.mediumtest.fs;
+
+import com.google.common.collect.ImmutableMap;
+import java.io.File;
+import java.io.IOException;
+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.SonarEdition;
+import org.sonar.api.batch.bootstrap.ProjectBuilder;
+import org.sonar.api.utils.MessageException;
+import org.sonar.scanner.mediumtest.AnalysisResult;
+import org.sonar.scanner.mediumtest.ScannerMediumTester;
+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;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+
+public class ProjectBuilderMediumIT {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ private ProjectBuilder projectBuilder = mock(ProjectBuilder.class);
+
+ @Rule
+ public ScannerMediumTester tester = new ScannerMediumTester()
+ .setEdition(SonarEdition.SONARCLOUD)
+ .registerPlugin("xoo", new XooPluginWithBuilder(projectBuilder))
+ .addRules(new XooRulesDefinition())
+ .addDefaultQProfile("xoo", "Sonar Way")
+ .addActiveRule("xoo", "OneIssuePerLine", null, "One issue per line", "MAJOR", "OneIssuePerLine.internal", "xoo");
+
+ private static class XooPluginWithBuilder extends XooPlugin {
+ private ProjectBuilder builder;
+
+ XooPluginWithBuilder(ProjectBuilder builder) {
+ this.builder = builder;
+ }
+
+ @Override
+ public void define(Context context) {
+ super.define(context);
+ context.addExtension(builder);
+ }
+ }
+
+ @Test
+ public void testProjectReactorValidation() throws IOException {
+ File baseDir = prepareProject();
+
+ doThrow(new IllegalStateException("My error message")).when(projectBuilder).build(any(ProjectBuilder.Context.class));
+
+ assertThatThrownBy(() -> tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", ".")
+ .put("sonar.xoo.enableProjectBuilder", "true")
+ .build())
+ .execute())
+ .isInstanceOf(MessageException.class)
+ .hasMessageContaining("Failed to execute project builder")
+ .hasCauseInstanceOf(IllegalStateException.class);
+ }
+
+ @Test
+ public void testProjectBuilder() throws IOException {
+ File baseDir = prepareProject();
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", ".")
+ .put("sonar.verbose", "true")
+ .put("sonar.xoo.enableProjectBuilder", "true")
+ .build())
+ .execute();
+ List<Issue> issues = result.issuesFor(result.inputFile("module1/src/sample.xoo"));
+ assertThat(issues).hasSize(10);
+
+ assertThat(issues)
+ .extracting("msg", "textRange.startLine", "gap")
+ .contains(tuple("This issue is generated on each line", 1, 0.0));
+
+ }
+
+ private File prepareProject() throws IOException {
+ File baseDir = temp.newFolder();
+ 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/it/java/org/sonar/scanner/mediumtest/highlighting/HighlightingMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/highlighting/HighlightingMediumIT.java
new file mode 100644
index 00000000000..811c883099f
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/highlighting/HighlightingMediumIT.java
@@ -0,0 +1,132 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.mediumtest.highlighting;
+
+import com.google.common.collect.ImmutableMap;
+import java.io.File;
+import java.io.IOException;
+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.sensor.highlighting.TypeOfText;
+import org.sonar.scanner.mediumtest.AnalysisResult;
+import org.sonar.scanner.mediumtest.ScannerMediumTester;
+import org.sonar.xoo.XooPlugin;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.sonar.scanner.mediumtest.ScannerMediumTester.AnalysisBuilder;
+
+public class HighlightingMediumIT {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public ScannerMediumTester tester = new ScannerMediumTester()
+ .registerPlugin("xoo", new XooPlugin())
+ .addDefaultQProfile("xoo", "Sonar Way");
+
+ @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, "1:0:2:0:s\n2:0:2:8:k");
+
+ AnalysisResult result = tester.newAnalysis()
+ .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())
+ .execute();
+
+ 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 saveTwice() throws IOException {
+ File baseDir = temp.newFolder();
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ File xooFile = new File(srcDir, "sample.xoo");
+ FileUtils.write(xooFile, "Sample xoo\ncontent plop");
+
+ AnalysisBuilder analysisBuilder = tester.newAnalysis()
+ .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")
+ .put("sonar.it.savedatatwice", "true")
+ .build());;
+
+ assertThatThrownBy(() -> analysisBuilder.execute())
+ .isInstanceOf(UnsupportedOperationException.class)
+ .hasMessageContaining("Trying to save highlighting twice for the same file is not supported");
+ }
+
+ @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, "1:0:1:10:s\n2:18:2:18:k");
+
+ AnalysisBuilder analysisBuilder = tester.newAnalysis()
+ .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());
+
+ assertThatThrownBy(() -> analysisBuilder.execute())
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessageContaining("Error processing line 2")
+ .hasCauseInstanceOf(IllegalArgumentException.class);
+ }
+
+}
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/ChecksMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/ChecksMediumIT.java
new file mode 100644
index 00000000000..30db61751f4
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/ChecksMediumIT.java
@@ -0,0 +1,110 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.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.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.batch.rule.LoadedActiveRule;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.scanner.mediumtest.AnalysisResult;
+import org.sonar.scanner.mediumtest.ScannerMediumTester;
+import org.sonar.scanner.protocol.output.ScannerReport.Issue;
+import org.sonar.xoo.XooPlugin;
+import org.sonar.xoo.rule.XooRulesDefinition;
+
+import static java.util.Collections.emptySet;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+
+public class ChecksMediumIT {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public ScannerMediumTester tester = new ScannerMediumTester()
+ .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"));
+
+ @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");
+
+ AnalysisResult result = tester.newAnalysis()
+ .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())
+ .execute();
+
+ List<Issue> issues = result.issuesFor(result.inputFile("src/sample.xoo"));
+ // If the message is the rule name. it's set by the CE. See SONAR-11531
+ assertThat(issues)
+ .extracting("msg", "textRange.startLine")
+ .containsOnly(
+ tuple("", 1),
+ tuple("", 2));
+
+ }
+
+ 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);
+ r.setDeprecatedKeys(emptySet());
+
+ Map<String, String> params = new HashMap<>();
+ params.put(paramKey, paramValue);
+ r.setParams(params);
+ return r;
+ }
+
+}
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/ExternalIssuesMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/ExternalIssuesMediumIT.java
new file mode 100644
index 00000000000..2f9d939f7d8
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/ExternalIssuesMediumIT.java
@@ -0,0 +1,184 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.mediumtest.issues;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+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.utils.log.LogTester;
+import org.sonar.scanner.mediumtest.AnalysisResult;
+import org.sonar.scanner.mediumtest.ScannerMediumTester;
+import org.sonar.scanner.protocol.Constants.Severity;
+import org.sonar.scanner.protocol.output.ScannerReport;
+import org.sonar.scanner.protocol.output.ScannerReport.ExternalIssue;
+import org.sonar.scanner.protocol.output.ScannerReport.Issue;
+import org.sonar.scanner.protocol.output.ScannerReport.IssueType;
+import org.sonar.xoo.XooPlugin;
+import org.sonar.xoo.rule.OneExternalIssuePerLineSensor;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+
+public class ExternalIssuesMediumIT {
+ @Rule
+ public LogTester logs = new LogTester();
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public ScannerMediumTester tester = new ScannerMediumTester()
+ .registerPlugin("xoo", new XooPlugin());
+
+ @Test
+ public void testOneIssuePerLine() throws Exception {
+ File projectDir = new File("test-resources/mediumtest/xoo/sample");
+ File tmpDir = temp.newFolder();
+ FileUtils.copyDirectory(projectDir, tmpDir);
+
+ AnalysisResult result = tester
+ .newAnalysis(new File(tmpDir, "sonar-project.properties"))
+ .property(OneExternalIssuePerLineSensor.ACTIVATE, "true")
+ .execute();
+
+ List<Issue> issues = result.issuesFor(result.inputFile("xources/hello/HelloJava.xoo"));
+ assertThat(issues).isEmpty();
+
+ List<ExternalIssue> externalIssues = result.externalIssuesFor(result.inputFile("xources/hello/HelloJava.xoo"));
+ assertThat(externalIssues).hasSize(8 /* lines */);
+
+ ExternalIssue issue = externalIssues.get(0);
+ assertThat(issue.getTextRange().getStartLine()).isEqualTo(issue.getTextRange().getStartLine());
+
+ assertThat(result.adHocRules()).isEmpty();
+ }
+
+ @Test
+ public void testOneIssuePerLine_register_ad_hoc_rule() throws Exception {
+ File projectDir = new File("test-resources/mediumtest/xoo/sample");
+ File tmpDir = temp.newFolder();
+ FileUtils.copyDirectory(projectDir, tmpDir);
+
+ AnalysisResult result = tester
+ .newAnalysis(new File(tmpDir, "sonar-project.properties"))
+ .property(OneExternalIssuePerLineSensor.ACTIVATE, "true")
+ .property(OneExternalIssuePerLineSensor.REGISTER_AD_HOC_RULE, "true")
+ .execute();
+
+ assertThat(result.adHocRules()).extracting(
+ ScannerReport.AdHocRule::getEngineId,
+ ScannerReport.AdHocRule::getRuleId,
+ ScannerReport.AdHocRule::getName,
+ ScannerReport.AdHocRule::getDescription,
+ ScannerReport.AdHocRule::getSeverity,
+ ScannerReport.AdHocRule::getType)
+ .containsExactlyInAnyOrder(
+ tuple(
+ OneExternalIssuePerLineSensor.ENGINE_ID,
+ OneExternalIssuePerLineSensor.RULE_ID,
+ "An ad hoc rule",
+ "blah blah",
+ Severity.BLOCKER,
+ IssueType.BUG));
+ }
+
+ @Test
+ public void testLoadIssuesFromJsonReport() throws URISyntaxException, IOException {
+ File projectDir = new File("test-resources/mediumtest/xoo/sample");
+ File tmpDir = temp.newFolder();
+ FileUtils.copyDirectory(projectDir, tmpDir);
+
+ AnalysisResult result = tester
+ .newAnalysis(new File(tmpDir, "sonar-project.properties"))
+ .property("sonar.externalIssuesReportPaths", "externalIssues.json")
+ .execute();
+
+ List<Issue> issues = result.issuesFor(result.inputFile("xources/hello/HelloJava.xoo"));
+ assertThat(issues).isEmpty();
+
+ List<ExternalIssue> externalIssues = result.externalIssuesFor(result.inputFile("xources/hello/HelloJava.xoo"));
+ assertThat(externalIssues).hasSize(2);
+
+ // precise issue location
+ ExternalIssue issue = externalIssues.get(0);
+ assertPreciseIssueLocation(issue);
+
+ // location on a line
+ issue = externalIssues.get(1);
+ assertIssueLocationLine(issue);
+
+ // One file-level issue in helloscala, with secondary location
+ List<ExternalIssue> externalIssues2 = result.externalIssuesFor(result.inputFile("xources/hello/helloscala.xoo"));
+ assertThat(externalIssues2).hasSize(1);
+
+ issue = externalIssues2.iterator().next();
+ assertSecondaryLocation(issue);
+
+ // one issue is located in a non-existing file
+ assertThat(logs.logs()).contains("External issues ignored for 1 unknown files, including: invalidFile");
+
+ }
+
+ private void assertSecondaryLocation(ExternalIssue issue) {
+ assertThat(issue.getFlowCount()).isEqualTo(2);
+ assertThat(issue.getMsg()).isEqualTo("fix the bug here");
+ assertThat(issue.getEngineId()).isEqualTo("externalXoo");
+ assertThat(issue.getRuleId()).isEqualTo("rule3");
+ assertThat(issue.getSeverity()).isEqualTo(Severity.MAJOR);
+ assertThat(issue.getType()).isEqualTo(IssueType.BUG);
+ assertThat(issue.hasTextRange()).isFalse();
+ assertThat(issue.getFlow(0).getLocationCount()).isOne();
+ assertThat(issue.getFlow(0).getLocation(0).getTextRange().getStartLine()).isOne();
+ assertThat(issue.getFlow(1).getLocationCount()).isOne();
+ assertThat(issue.getFlow(1).getLocation(0).getTextRange().getStartLine()).isEqualTo(3);
+ }
+
+ private void assertIssueLocationLine(ExternalIssue issue) {
+ assertThat(issue.getFlowCount()).isZero();
+ assertThat(issue.getMsg()).isEqualTo("fix the bug here");
+ assertThat(issue.getEngineId()).isEqualTo("externalXoo");
+ assertThat(issue.getRuleId()).isEqualTo("rule2");
+ assertThat(issue.getSeverity()).isEqualTo(Severity.CRITICAL);
+ assertThat(issue.getType()).isEqualTo(IssueType.BUG);
+ assertThat(issue.getEffort()).isZero();
+ assertThat(issue.getTextRange().getStartLine()).isEqualTo(3);
+ assertThat(issue.getTextRange().getEndLine()).isEqualTo(3);
+ assertThat(issue.getTextRange().getStartOffset()).isZero();
+ assertThat(issue.getTextRange().getEndOffset()).isEqualTo(24);
+ }
+
+ private void assertPreciseIssueLocation(ExternalIssue issue) {
+ assertThat(issue.getFlowCount()).isZero();
+ assertThat(issue.getMsg()).isEqualTo("fix the issue here");
+ assertThat(issue.getEngineId()).isEqualTo("externalXoo");
+ assertThat(issue.getRuleId()).isEqualTo("rule1");
+ assertThat(issue.getSeverity()).isEqualTo(Severity.MAJOR);
+ assertThat(issue.getEffort()).isEqualTo(50L);
+ assertThat(issue.getType()).isEqualTo(IssueType.CODE_SMELL);
+ assertThat(issue.getTextRange().getStartLine()).isEqualTo(5);
+ assertThat(issue.getTextRange().getEndLine()).isEqualTo(5);
+ assertThat(issue.getTextRange().getStartOffset()).isEqualTo(3);
+ assertThat(issue.getTextRange().getEndOffset()).isEqualTo(41);
+ }
+}
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/IssuesMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/IssuesMediumIT.java
new file mode 100644
index 00000000000..feaf921d141
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/IssuesMediumIT.java
@@ -0,0 +1,452 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.mediumtest.issues;
+
+import com.google.common.collect.ImmutableMap;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+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.rule.LoadedActiveRule;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.scanner.mediumtest.AnalysisResult;
+import org.sonar.scanner.mediumtest.ScannerMediumTester;
+import org.sonar.scanner.protocol.output.ScannerReport.ExternalIssue;
+import org.sonar.scanner.protocol.output.ScannerReport.Issue;
+import org.sonar.xoo.XooPlugin;
+import org.sonar.xoo.rule.HasTagSensor;
+import org.sonar.xoo.rule.OneExternalIssueOnProjectSensor;
+import org.sonar.xoo.rule.OneExternalIssuePerLineSensor;
+import org.sonar.xoo.rule.XooRulesDefinition;
+
+import static java.util.Collections.emptySet;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+
+public class IssuesMediumIT {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public LogTester logTester = new LogTester();
+
+ @Rule
+ public ScannerMediumTester tester = new ScannerMediumTester()
+ .registerPlugin("xoo", new XooPlugin())
+ .addDefaultQProfile("xoo", "Sonar Way")
+ .addRules(new XooRulesDefinition())
+ .addActiveRule("xoo", "OneIssuePerLine", null, "One issue per line", "MAJOR", "OneIssuePerLine.internal", "xoo");
+
+ @Test
+ public void testOneIssuePerLine() throws Exception {
+ File projectDir = new File("test-resources/mediumtest/xoo/sample");
+ File tmpDir = temp.newFolder();
+ FileUtils.copyDirectory(projectDir, tmpDir);
+
+ AnalysisResult result = tester
+ .newAnalysis(new File(tmpDir, "sonar-project.properties"))
+ .execute();
+
+ List<Issue> issues = result.issuesFor(result.inputFile("xources/hello/HelloJava.xoo"));
+ assertThat(issues).hasSize(8 /* lines */);
+
+ List<ExternalIssue> externalIssues = result.externalIssuesFor(result.inputFile("xources/hello/HelloJava.xoo"));
+ assertThat(externalIssues).isEmpty();
+ }
+
+ @Test
+ public void testOneExternalIssuePerLine() throws Exception {
+ File projectDir = new File("test-resources/mediumtest/xoo/sample");
+ File tmpDir = temp.newFolder();
+ FileUtils.copyDirectory(projectDir, tmpDir);
+
+ AnalysisResult result = tester
+ .newAnalysis(new File(tmpDir, "sonar-project.properties"))
+ .property(OneExternalIssuePerLineSensor.ACTIVATE, "true")
+ .execute();
+
+ List<ExternalIssue> externalIssues = result.externalIssuesFor(result.inputFile("xources/hello/HelloJava.xoo"));
+ assertThat(externalIssues).hasSize(8 /* lines */);
+ }
+
+ @Test
+ public void testOneExternalIssueOnProject() throws Exception {
+ File projectDir = new File("test-resources/mediumtest/xoo/sample");
+ File tmpDir = temp.newFolder();
+ FileUtils.copyDirectory(projectDir, tmpDir);
+
+ AnalysisResult result = tester
+ .newAnalysis(new File(tmpDir, "sonar-project.properties"))
+ .property(OneExternalIssueOnProjectSensor.ACTIVATE, "true")
+ .execute();
+
+ List<ExternalIssue> externalIssues = result.externalIssuesFor(result.project());
+ assertThat(externalIssues).hasSize(1);
+ }
+
+ @Test
+ public void findActiveRuleByInternalKey() throws Exception {
+ File projectDir = new File("test-resources/mediumtest/xoo/sample");
+ File tmpDir = temp.newFolder();
+ FileUtils.copyDirectory(projectDir, tmpDir);
+
+ AnalysisResult result = tester
+ .newAnalysis(new File(tmpDir, "sonar-project.properties"))
+ .property("sonar.xoo.internalKey", "OneIssuePerLine.internal")
+ .execute();
+
+ 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("test-resources/mediumtest/xoo/sample");
+ File tmpDir = temp.newFolder();
+ FileUtils.copyDirectory(projectDir, tmpDir);
+
+ AnalysisResult result = tester
+ .newAnalysis(new File(tmpDir, "sonar-project.properties"))
+ .property("sonar.oneIssuePerLine.forceSeverity", "CRITICAL")
+ .execute();
+
+ 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 testIssueExclusionByRegexp() throws Exception {
+ File projectDir = new File("test-resources/mediumtest/xoo/sample");
+ File tmpDir = temp.newFolder();
+ FileUtils.copyDirectory(projectDir, tmpDir);
+
+ AnalysisResult result = tester
+ .newAnalysis(new File(tmpDir, "sonar-project.properties"))
+ .property("sonar.issue.ignore.allfile", "1")
+ .property("sonar.issue.ignore.allfile.1.fileRegexp", "object")
+ .execute();
+
+ 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 testIssueExclusionByBlock() throws Exception {
+ File baseDir = temp.newFolder();
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ File xooFile = new File(srcDir, "sample.xoo");
+ FileUtils.write(xooFile, "1\nSONAR-OFF 2\n3\n4\n5\nSONAR-ON 6\n7\n8\n9\n10", StandardCharsets.UTF_8);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .build())
+ .property("sonar.issue.ignore.block", "1")
+ .property("sonar.issue.ignore.block.1.beginBlockRegexp", "SON.*-OFF")
+ .property("sonar.issue.ignore.block.1.endBlockRegexp", "SON.*-ON")
+ .execute();
+
+ List<Issue> issues = result.issuesFor(result.inputFile("src/sample.xoo"));
+ assertThat(issues).hasSize(5);
+ assertThat(issues)
+ .extracting("textRange.startLine")
+ .containsExactlyInAnyOrder(1, 7, 8, 9, 10);
+ }
+
+ @Test
+ public void testIssueExclusionByIgnoreMultiCriteria() throws Exception {
+ File baseDir = temp.newFolder();
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ activateTODORule();
+
+ File xooFile1 = new File(srcDir, "sample1.xoo");
+ FileUtils.write(xooFile1, "1\n2\n3 TODO\n4\n5\n6 TODO\n7\n8\n9\n10", StandardCharsets.UTF_8);
+ File xooFile11 = new File(srcDir, "sample11.xoo");
+ FileUtils.write(xooFile11, "1\n2\n3 TODO\n4\n5\n6 TODO\n7\n8\n9\n10", StandardCharsets.UTF_8);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .build())
+ .property("sonar.issue.ignore.multicriteria", "1,2")
+ .property("sonar.issue.ignore.multicriteria.1.ruleKey", "xoo:HasTag")
+ .property("sonar.issue.ignore.multicriteria.1.resourceKey", "src/sample11.xoo")
+ .property("sonar.issue.ignore.multicriteria.2.ruleKey", "xoo:One*")
+ .property("sonar.issue.ignore.multicriteria.2.resourceKey", "src/sample?.xoo")
+ .execute();
+
+ List<Issue> issues = result.issuesFor(result.inputFile("src/sample1.xoo"));
+ assertThat(issues).hasSize(2);
+
+ issues = result.issuesFor(result.inputFile("src/sample11.xoo"));
+ assertThat(issues).hasSize(10);
+ }
+
+ @Test
+ public void warn_user_for_outdated_IssueExclusionByIgnoreMultiCriteria() throws Exception {
+ File baseDir = temp.getRoot();
+ File baseDirModuleA = new File(baseDir, "moduleA");
+ File baseDirModuleB = new File(baseDir, "moduleB");
+ File srcDirA = new File(baseDirModuleA, "src");
+ srcDirA.mkdirs();
+ File srcDirB = new File(baseDirModuleB, "src");
+ srcDirB.mkdirs();
+
+ File xooFileA = new File(srcDirA, "sampleA.xoo");
+ FileUtils.write(xooFileA, "1\n2\n3\n4\n5\n6 TODO\n7\n8\n9\n10", StandardCharsets.UTF_8);
+ File xooFileB = new File(srcDirB, "sampleB.xoo");
+ FileUtils.write(xooFileB, "1\n2\n3\n4\n5\n6 TODO\n7\n8\n9\n10", StandardCharsets.UTF_8);
+
+ tester
+ .addProjectServerSettings("sonar.issue.ignore.multicriteria", "1")
+ .addProjectServerSettings("sonar.issue.ignore.multicriteria.1.ruleKey", "*")
+ .addProjectServerSettings("sonar.issue.ignore.multicriteria.1.resourceKey", "src/sampleA.xoo");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.modules", "moduleA,moduleB")
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+ assertThat(logTester.logs(LoggerLevel.WARN)).contains(
+ "Specifying module-relative paths at project level in property 'sonar.issue.ignore.multicriteria' is deprecated. To continue matching files like 'moduleA/src/sampleA.xoo', update this property so that patterns refer to project-relative paths.");
+
+ List<Issue> issues = result.issuesFor(result.inputFile("moduleA/src/sampleA.xoo"));
+ assertThat(issues).isEmpty();
+
+ issues = result.issuesFor(result.inputFile("moduleB/src/sampleB.xoo"));
+ assertThat(issues).hasSize(10);
+ }
+
+ @Test
+ public void warn_user_for_unsupported_module_level_IssueExclusion() throws Exception {
+ File baseDir = temp.getRoot();
+ File baseDirModuleA = new File(baseDir, "moduleA");
+ File baseDirModuleB = new File(baseDir, "moduleB");
+ File srcDirA = new File(baseDirModuleA, "src");
+ srcDirA.mkdirs();
+ File srcDirB = new File(baseDirModuleB, "src");
+ srcDirB.mkdirs();
+
+ File xooFileA = new File(srcDirA, "sampleA.xoo");
+ FileUtils.write(xooFileA, "1\n2\n3\n4\n5\n6 TODO\n7\n8\n9\n10", StandardCharsets.UTF_8);
+ File xooFileB = new File(srcDirB, "sampleB.xoo");
+ FileUtils.write(xooFileB, "1\n2\n3\n4\n5\n6 TODO\n7\n8\n9\n10", StandardCharsets.UTF_8);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.modules", "moduleA,moduleB")
+ .put("sonar.sources", "src")
+ .put("sonar.scm.disabled", "true")
+ .put("sonar.issue.ignore.multicriteria", "1")
+ .put("sonar.issue.ignore.multicriteria.1.ruleKey", "*")
+ .put("sonar.issue.ignore.multicriteria.1.resourceKey", "*")
+ .build())
+ .execute();
+
+ assertThat(logTester.logs(LoggerLevel.WARN)).isEmpty();
+
+ result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.modules", "moduleA,moduleB")
+ .put("sonar.sources", "src")
+ .put("sonar.scm.disabled", "true")
+ .put("moduleA.sonar.issue.ignore.multicriteria", "1")
+ .put("moduleA.sonar.issue.ignore.multicriteria.1.ruleKey", "*")
+ .put("moduleA.sonar.issue.ignore.multicriteria.1.resourceKey", "*")
+ .build())
+ .execute();
+
+ assertThat(logTester.logs(LoggerLevel.WARN)).containsOnly(
+ "Specifying issue exclusions at module level is not supported anymore. Configure the property 'sonar.issue.ignore.multicriteria' and any other issue exclusions at project level.");
+
+ List<Issue> issues = result.issuesFor(result.inputFile("moduleA/src/sampleA.xoo"));
+ assertThat(issues).hasSize(10);
+
+ issues = result.issuesFor(result.inputFile("moduleB/src/sampleB.xoo"));
+ assertThat(issues).hasSize(10);
+
+ // SONAR-11850 The Maven scanner replicates properties defined on the root module to all modules
+ logTester.clear();
+ result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.modules", "moduleA,moduleB")
+ .put("sonar.sources", "src")
+ .put("sonar.scm.disabled", "true")
+ .put("sonar.issue.ignore.multicriteria", "1")
+ .put("sonar.issue.ignore.multicriteria.1.ruleKey", "*")
+ .put("sonar.issue.ignore.multicriteria.1.resourceKey", "*")
+ .put("moduleA.sonar.issue.ignore.multicriteria", "1")
+ .put("moduleA.sonar.issue.ignore.multicriteria.1.ruleKey", "*")
+ .put("moduleA.sonar.issue.ignore.multicriteria.1.resourceKey", "*")
+ .build())
+ .execute();
+
+ assertThat(logTester.logs(LoggerLevel.WARN)).isEmpty();
+ }
+
+ @Test
+ public void testIssueExclusionByEnforceMultiCriteria() throws Exception {
+ File baseDir = temp.newFolder();
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ activateTODORule();
+
+ File xooFile1 = new File(srcDir, "sample1.xoo");
+ FileUtils.write(xooFile1, "1\n2\n3 TODO\n4\n5\n6 TODO\n7\n8\n9\n10", StandardCharsets.UTF_8);
+ File xooFile11 = new File(srcDir, "sample11.xoo");
+ FileUtils.write(xooFile11, "1\n2\n3 TODO\n4\n5\n6 TODO\n7\n8\n9\n10", StandardCharsets.UTF_8);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .build())
+ .property("sonar.issue.enforce.multicriteria", "1,2")
+ .property("sonar.issue.enforce.multicriteria.1.ruleKey", "xoo:HasTag")
+ .property("sonar.issue.enforce.multicriteria.1.resourceKey", "src/sample11.xoo")
+ .property("sonar.issue.enforce.multicriteria.2.ruleKey", "xoo:One*")
+ .property("sonar.issue.enforce.multicriteria.2.resourceKey", "src/sample?.xoo")
+ .execute();
+
+ List<Issue> issues = result.issuesFor(result.inputFile("src/sample1.xoo"));
+ assertThat(issues).hasSize(10);
+
+ issues = result.issuesFor(result.inputFile("src/sample11.xoo"));
+ assertThat(issues).hasSize(2);
+ }
+
+ @Test
+ public void warn_user_for_outdated_IssueExclusionByEnforceMultiCriteria() throws Exception {
+ File baseDir = temp.getRoot();
+ File baseDirModuleA = new File(baseDir, "moduleA");
+ File baseDirModuleB = new File(baseDir, "moduleB");
+ File srcDirA = new File(baseDirModuleA, "src");
+ srcDirA.mkdirs();
+ File srcDirB = new File(baseDirModuleB, "src");
+ srcDirB.mkdirs();
+
+ File xooFileA = new File(srcDirA, "sampleA.xoo");
+ FileUtils.write(xooFileA, "1\n2\n3\n4\n5\n6 TODO\n7\n8\n9\n10", StandardCharsets.UTF_8);
+ File xooFileB = new File(srcDirB, "sampleB.xoo");
+ FileUtils.write(xooFileB, "1\n2\n3\n4\n5\n6 TODO\n7\n8\n9\n10", StandardCharsets.UTF_8);
+
+ tester
+ .addProjectServerSettings("sonar.issue.enforce.multicriteria", "1")
+ .addProjectServerSettings("sonar.issue.enforce.multicriteria.1.ruleKey", "*")
+ .addProjectServerSettings("sonar.issue.enforce.multicriteria.1.resourceKey", "src/sampleA.xoo");
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.modules", "moduleA,moduleB")
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+ assertThat(logTester.logs(LoggerLevel.WARN)).contains(
+ "Specifying module-relative paths at project level in property 'sonar.issue.enforce.multicriteria' is deprecated. To continue matching files like 'moduleA/src/sampleA.xoo', update this property so that patterns refer to project-relative paths.");
+
+ List<Issue> issues = result.issuesFor(result.inputFile("moduleA/src/sampleA.xoo"));
+ assertThat(issues).hasSize(10);
+
+ issues = result.issuesFor(result.inputFile("moduleB/src/sampleB.xoo"));
+ assertThat(issues).isEmpty();
+ }
+
+ private void activateTODORule() {
+ LoadedActiveRule r = new LoadedActiveRule();
+ r.setRuleKey(RuleKey.of("xoo", HasTagSensor.RULE_KEY));
+ r.setName("TODO");
+ r.setLanguage("xoo");
+ r.setSeverity("MAJOR");
+ r.setDeprecatedKeys(emptySet()
+ );
+ r.setParams(ImmutableMap.of("tag", "TODO"));
+ tester.activateRule(r);
+ }
+
+ @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", StandardCharsets.UTF_8);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+ List<Issue> issues = result.issuesFor(result.inputFile("src/sample.xoo"));
+ assertThat(issues).hasSize(10);
+ assertThat(issues)
+ .extracting("msg", "textRange.startLine", "gap")
+ .contains(tuple("This issue is generated on each line", 1, 0.0));
+ }
+
+ @Test
+ public void testIssueFilter() throws Exception {
+ File projectDir = new File("test-resources/mediumtest/xoo/sample");
+ File tmpDir = temp.newFolder();
+ FileUtils.copyDirectory(projectDir, tmpDir);
+
+ AnalysisResult result = tester
+ .newAnalysis(new File(tmpDir, "sonar-project.properties"))
+ .property("sonar.xoo.excludeAllIssuesOnOddLines", "true")
+ .execute();
+
+ List<Issue> issues = result.issuesFor(result.inputFile("xources/hello/HelloJava.xoo"));
+ assertThat(issues).hasSize(4 /* even lines */);
+ }
+
+}
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/IssuesOnDirMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/IssuesOnDirMediumIT.java
new file mode 100644
index 00000000000..80553376649
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/IssuesOnDirMediumIT.java
@@ -0,0 +1,102 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.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.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.scanner.mediumtest.AnalysisResult;
+import org.sonar.scanner.mediumtest.ScannerMediumTester;
+import org.sonar.xoo.XooPlugin;
+import org.sonar.xoo.rule.XooRulesDefinition;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class IssuesOnDirMediumIT {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public ScannerMediumTester tester = new ScannerMediumTester()
+ .registerPlugin("xoo", new XooPlugin())
+ .addDefaultQProfile("xoo", "Sonar Way")
+ .addRules(new XooRulesDefinition())
+ .addActiveRule("xoo", "OneIssueOnDirPerFile", null, "One issue per line", "MINOR", "xoo", "xoo");
+
+ @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");
+
+ AnalysisResult result = tester.newAnalysis()
+ .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())
+ .execute();
+
+ assertThat(result.issuesFor(result.project())).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");
+
+ AnalysisResult result = tester.newAnalysis()
+ .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())
+ .execute();
+
+ assertThat(result.issuesFor(result.project())).hasSize(2);
+ }
+
+}
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/IssuesOnModuleMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/IssuesOnModuleMediumIT.java
new file mode 100644
index 00000000000..b83481d5729
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/IssuesOnModuleMediumIT.java
@@ -0,0 +1,73 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.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.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.scanner.mediumtest.AnalysisResult;
+import org.sonar.scanner.mediumtest.ScannerMediumTester;
+import org.sonar.xoo.XooPlugin;
+import org.sonar.xoo.rule.XooRulesDefinition;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class IssuesOnModuleMediumIT {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public ScannerMediumTester tester = new ScannerMediumTester()
+ .registerPlugin("xoo", new XooPlugin())
+ .addDefaultQProfile("xoo", "Sonar Way")
+ .addRules(new XooRulesDefinition())
+ .addActiveRule("xoo", "OneIssuePerModule", null, "One issue per module", "MINOR", "xoo", "xoo");
+
+ @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");
+
+ AnalysisResult result = tester.newAnalysis()
+ .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())
+ .execute();
+
+ assertThat(result.issuesFor(result.project())).hasSize(1);
+ }
+
+}
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/MultilineIssuesMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/MultilineIssuesMediumIT.java
new file mode 100644
index 00000000000..5d69b83c7c8
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/MultilineIssuesMediumIT.java
@@ -0,0 +1,137 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.mediumtest.issues;
+
+import java.io.File;
+import java.util.List;
+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.scanner.mediumtest.AnalysisResult;
+import org.sonar.scanner.mediumtest.ScannerMediumTester;
+import org.sonar.scanner.protocol.output.ScannerReport.Flow;
+import org.sonar.scanner.protocol.output.ScannerReport.FlowType;
+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;
+import static org.assertj.core.api.Assertions.tuple;
+
+public class MultilineIssuesMediumIT {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public ScannerMediumTester tester = new ScannerMediumTester()
+ .registerPlugin("xoo", new XooPlugin())
+ .addRules(new XooRulesDefinition())
+ .addDefaultQProfile("xoo", "Sonar Way")
+ .addActiveRule("xoo", "MultilineIssue", null, "Multinile Issue", "MAJOR", null, "xoo");
+
+ private AnalysisResult result;
+
+ @Before
+ public void prepare() throws Exception {
+ File projectDir = new File("test-resources/mediumtest/xoo/sample-multiline");
+ File tmpDir = temp.getRoot();
+ FileUtils.copyDirectory(projectDir, tmpDir);
+
+ result = tester
+ .newAnalysis(new File(tmpDir, "sonar-project.properties"))
+ .execute();
+ }
+
+ @Test
+ public void testIssueRange() {
+ List<Issue> issues = result.issuesFor(result.inputFile("xources/hello/Single.xoo"));
+ assertThat(issues).hasSize(1);
+ Issue issue = issues.get(0);
+ assertThat(issue.getMsg()).isEqualTo("Primary location of the issue in xoo code");
+ 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() {
+ List<Issue> issues = result.issuesFor(result.inputFile("xources/hello/Multiline.xoo"));
+ assertThat(issues).hasSize(1);
+ Issue issue = issues.get(0);
+ assertThat(issue.getMsg()).isEqualTo("Primary location of the issue in xoo code");
+ 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() {
+ List<Issue> issues = result.issuesFor(result.inputFile("xources/hello/Multiple.xoo"));
+ assertThat(issues).hasSize(1);
+ Issue issue = issues.get(0);
+ assertThat(issue.getMsg()).isEqualTo("Primary location of the issue in xoo code");
+ 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("Xoo code, 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() {
+ 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
+ }
+
+ @Test
+ public void testFlowsWithTypes() {
+ List<Issue> issues = result.issuesFor(result.inputFile("xources/hello/FlowTypes.xoo"));
+ assertThat(issues).hasSize(1);
+ Issue issue = issues.get(0);
+ assertThat(issue.getFlowList()).hasSize(3);
+
+ assertThat(issue.getFlowList()).extracting(Flow::getType, Flow::getDescription, f -> f.getLocationList().size())
+ .containsExactly(
+ tuple(FlowType.DATA, "flow #1", 1),
+ tuple(FlowType.UNDEFINED, "", 1),
+ tuple(FlowType.EXECUTION, "flow #3", 1));
+ }
+}
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/PreviewMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/PreviewMediumIT.java
new file mode 100644
index 00000000000..6cd81792a93
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/PreviewMediumIT.java
@@ -0,0 +1,66 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.mediumtest.issues;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.MessageException;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.scanner.mediumtest.ScannerMediumTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+
+public class PreviewMediumIT {
+
+ @Rule
+ public LogTester logTester = new LogTester();
+
+ @Rule
+ public ScannerMediumTester tester = new ScannerMediumTester();
+
+ @Test
+ public void failWhenUsingPreviewMode() {
+ try {
+ tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.analysis.mode", "preview").build())
+ .execute();
+ fail("Expected exception");
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(MessageException.class).hasMessage("The preview mode, along with the 'sonar.analysis.mode' parameter, is no more supported. You should stop using this parameter.");
+ }
+ }
+
+ @Test
+ public void failWhenUsingIssuesMode() {
+ try {
+ tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.analysis.mode", "issues").build())
+ .execute();
+ fail("Expected exception");
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(MessageException.class).hasMessage("The preview mode, along with the 'sonar.analysis.mode' parameter, is no more supported. You should stop using this parameter.");
+ }
+ }
+
+}
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/log/ExceptionHandlingMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/log/ExceptionHandlingMediumIT.java
new file mode 100644
index 00000000000..64c41253437
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/log/ExceptionHandlingMediumIT.java
@@ -0,0 +1,102 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.mediumtest.log;
+
+import java.util.Collections;
+import java.util.Map;
+import javax.annotation.Priority;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.sonar.api.utils.MessageException;
+import org.sonar.batch.bootstrapper.Batch;
+import org.sonar.batch.bootstrapper.EnvironmentInformation;
+import org.sonar.scanner.repository.settings.GlobalSettingsLoader;
+import org.springframework.beans.factory.UnsatisfiedDependencyException;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+public class ExceptionHandlingMediumIT {
+
+ private Batch batch;
+ private static ErrorGlobalSettingsLoader loader;
+
+ @BeforeClass
+ public static void beforeClass() {
+ loader = new ErrorGlobalSettingsLoader();
+ }
+
+ public void setUp(boolean verbose) {
+ Batch.Builder builder = Batch.builder()
+ .setEnableLoggingConfiguration(true)
+ .addComponents(
+ loader,
+ new EnvironmentInformation("mediumTest", "1.0"));
+
+ if (verbose) {
+ builder.setGlobalProperties(Collections.singletonMap("sonar.verbose", "true"));
+ }
+ batch = builder.build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ setUp(false);
+ loader.withCause = false;
+
+ assertThatThrownBy(() -> batch.execute())
+ .isInstanceOf(MessageException.class)
+ .hasMessage("Error loading settings");
+ }
+
+ @Test
+ public void testWithCause() throws Exception {
+ setUp(false);
+ loader.withCause = true;
+
+ assertThatThrownBy(() -> batch.execute())
+ .isInstanceOf(MessageException.class)
+ .hasMessage("Error loading settings")
+ .hasCauseInstanceOf(Throwable.class)
+ .hasRootCauseMessage("Code 401");
+ }
+
+ @Test
+ public void testWithVerbose() {
+ setUp(true);
+ assertThatThrownBy(() -> batch.execute())
+ .isInstanceOf(UnsatisfiedDependencyException.class)
+ .hasMessageContaining("Error loading settings");
+ }
+
+ @Priority(1)
+ private static class ErrorGlobalSettingsLoader implements GlobalSettingsLoader {
+ boolean withCause = false;
+
+ @Override
+ public Map<String, String> loadGlobalSettings() {
+ if (withCause) {
+ IllegalStateException cause = new IllegalStateException("Code 401");
+ throw MessageException.of("Error loading settings", cause);
+ } else {
+ throw MessageException.of("Error loading settings");
+ }
+ }
+ }
+}
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/log/LogListenerIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/log/LogListenerIT.java
new file mode 100644
index 00000000000..e2a63e2ac6c
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/log/LogListenerIT.java
@@ -0,0 +1,256 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.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.nio.charset.StandardCharsets;
+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.TemporaryFolder;
+import org.sonar.batch.bootstrapper.LogOutput;
+import org.sonar.batch.bootstrapper.LogOutput.Level;
+import org.sonar.scanner.mediumtest.ScannerMediumTester;
+import org.sonar.xoo.XooPlugin;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+
+public class LogListenerIT {
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ private Pattern simpleTimePattern = Pattern.compile("\\d{2}:\\d{2}:\\d{2}");
+ private List<LogEvent> logOutput;
+ private StringBuilder logOutputStr;
+ private ByteArrayOutputStream stdOutTarget;
+ private ByteArrayOutputStream stdErrTarget;
+ private static PrintStream savedStdOut;
+ private static PrintStream savedStdErr;
+
+ @Rule
+ public ScannerMediumTester tester = new ScannerMediumTester()
+ .registerPlugin("xoo", new XooPlugin())
+ .addDefaultQProfile("xoo", "Sonar Way")
+ .setLogOutput(new SimpleLogListener());
+
+ 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() {
+ System.out.flush();
+ System.err.flush();
+ stdOutTarget = new ByteArrayOutputStream();
+ stdErrTarget = new ByteArrayOutputStream();
+ System.setOut(new PrintStream(stdOutTarget));
+ System.setErr(new PrintStream(stdErrTarget));
+ // logger from the batch might write to it asynchronously
+ logOutput = Collections.synchronizedList(new LinkedList<>());
+ logOutputStr = new StringBuilder();
+
+ 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(new String(stdOutTarget.toByteArray())).isEmpty();
+ assertThat(new String(stdErrTarget.toByteArray())).isEmpty();
+ }
+
+ /**
+ * Check that log message is not formatted, i.e. has no log level and timestamp.
+ */
+ private void assertMsgClean(String msg) {
+ // FP: [JOURNAL_FLUSHER] WARNING Journal flush operation took 2,093ms last 8 cycles average is 262ms
+ if (msg.contains("[JOURNAL_FLUSHER]")) {
+ return;
+ }
+
+ for (Level l : Level.values()) {
+ assertThat(msg).doesNotContain(l.toString());
+ }
+
+ Matcher matcher = simpleTimePattern.matcher(msg);
+ assertThat(matcher.find()).isFalse();
+ }
+
+ @Test
+ public void testChangeLogForAnalysis() throws IOException {
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ File xooFile = new File(srcDir, "sample.xoo");
+ FileUtils.write(xooFile, "Sample xoo\ncontent", StandardCharsets.UTF_8);
+
+ tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .put("sonar.verbose", "true")
+ .build())
+ .execute();
+
+ synchronized (logOutput) {
+ 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", StandardCharsets.UTF_8);
+
+ tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+ 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", StandardCharsets.UTF_8);
+
+ tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+ assertNoStdOutput();
+
+ synchronized (logOutput) {
+ for (LogEvent e : logOutput) {
+ assertMsgClean(e.msg);
+ savedStdOut.println("[captured]" + e.level + " " + e.msg);
+ }
+ }
+ }
+
+ // SONAR-7540
+ @Test
+ public void testStackTrace() throws IOException {
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ File xooFile = new File(srcDir, "sample.xoo");
+ FileUtils.write(xooFile, "Sample xoo\ncontent");
+ File xooFileMeasure = new File(srcDir, "sample.xoo.measures");
+ FileUtils.write(xooFileMeasure, "foo:bar", StandardCharsets.UTF_8);
+
+ try {
+ tester.newAnalysis()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+ fail("Expected exception");
+ } catch (Exception e) {
+ assertThat(e.getMessage()).contains("Error processing line 1");
+ }
+
+ assertNoStdOutput();
+
+ synchronized (logOutput) {
+ for (LogEvent e : logOutput) {
+ if (e.level == Level.ERROR) {
+ assertThat(e.msg).contains("Error processing line 1 of file", "src" + File.separator + "sample.xoo.measures",
+ "java.lang.IllegalStateException: Unknown metric with key: foo",
+ "at org.sonar.xoo.lang.MeasureSensor.saveMeasure");
+
+ }
+ }
+ }
+ }
+
+ 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;
+ Level level;
+
+ LogEvent(String msg, Level level) {
+ this.msg = msg;
+ this.level = level;
+ }
+ }
+}
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/measures/MeasuresMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/measures/MeasuresMediumIT.java
new file mode 100644
index 00000000000..c3f4fb1768e
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/measures/MeasuresMediumIT.java
@@ -0,0 +1,182 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.mediumtest.measures;
+
+import com.google.common.collect.ImmutableMap;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+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.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.scanner.mediumtest.AnalysisResult;
+import org.sonar.scanner.mediumtest.ScannerMediumTester;
+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;
+import static org.junit.Assert.fail;
+
+public class MeasuresMediumIT {
+
+ @Rule
+ public LogTester logTester = new LogTester();
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ private File baseDir;
+ private File srcDir;
+
+ @Rule
+ public ScannerMediumTester tester = new ScannerMediumTester()
+ .registerPlugin("xoo", new XooPlugin())
+ .addDefaultQProfile("xoo", "Sonar Way");
+
+ @Before
+ public void setUp() throws Exception {
+ baseDir = temp.newFolder();
+ srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+ }
+
+ @Test
+ public void failIfTryingToSaveServerSideMeasure() throws IOException {
+ File xooFile = new File(srcDir, "sample.xoo");
+ FileUtils.write(xooFile, "Sample xoo\n\ncontent", StandardCharsets.UTF_8);
+
+ File measures = new File(srcDir, "sample.xoo.measures");
+ FileUtils.write(measures, "new_lines:2", StandardCharsets.UTF_8);
+
+ try {
+ tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+ fail("Expected exception");
+ } catch (Exception e) {
+ assertThat(e)
+ .hasCauseInstanceOf(UnsupportedOperationException.class)
+ .hasStackTraceContaining("Metric 'new_lines' should not be computed by a Sensor");
+ }
+ }
+
+ @Test
+ public void lineMeasures() throws IOException {
+ File xooFile = new File(srcDir, "sample.xoo");
+ FileUtils.write(xooFile, "Sample xoo\n\n\ncontent", StandardCharsets.UTF_8);
+
+ File lineMeasures = new File(srcDir, "sample.xoo.linemeasures");
+ FileUtils.write(lineMeasures, "ncloc_data:1=1;2=0;4=1", StandardCharsets.UTF_8);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+ Map<String, List<Measure>> allMeasures = result.allMeasures();
+
+ assertThat(allMeasures.get("com.foo.project:src/sample.xoo")).extracting("metricKey", "intValue.value", "stringValue.value")
+ .containsExactly(tuple("ncloc_data", 0, "1=1;4=1"));
+ }
+
+ @Test
+ public void projectLevelMeasures() throws IOException {
+ File xooFile = new File(srcDir, "sample.xoo");
+ FileUtils.write(xooFile, "Sample xoo\n\n\ncontent", StandardCharsets.UTF_8);
+
+ File projectMeasures = new File(baseDir, "module.measures");
+ FileUtils.write(projectMeasures, "tests:10", StandardCharsets.UTF_8);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+ Map<String, List<Measure>> allMeasures = result.allMeasures();
+
+ assertThat(allMeasures.get("com.foo.project"))
+ .extracting("metricKey", "intValue.value", "stringValue.value")
+ .containsExactly(tuple("tests", 10, ""));
+ }
+
+ @Test
+ public void warnWhenSavingFolderMeasure() throws IOException {
+ File xooFile = new File(srcDir, "sample.xoo");
+ FileUtils.write(xooFile, "Sample xoo\n\n\ncontent", StandardCharsets.UTF_8);
+
+ File folderMeasures = new File(srcDir, "folder.measures");
+ FileUtils.write(folderMeasures, "tests:10", StandardCharsets.UTF_8);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+
+ assertThat(logTester.logs(LoggerLevel.WARN)).contains("Storing measures on folders or modules is deprecated. Provided value of metric 'tests' is ignored.");
+ }
+
+ @Test
+ public void warnWhenSavingModuleMeasure() throws IOException {
+ File moduleDir = new File(baseDir, "moduleA");
+ moduleDir.mkdirs();
+
+ srcDir = new File(moduleDir, "src");
+
+ File xooFile = new File(srcDir, "sample.xoo");
+ FileUtils.write(xooFile, "Sample xoo\n\n\ncontent", StandardCharsets.UTF_8);
+
+ File moduleMeasures = new File(moduleDir, "module.measures");
+ FileUtils.write(moduleMeasures, "tests:10", StandardCharsets.UTF_8);
+
+ AnalysisResult result = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.modules", "moduleA")
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+
+ assertThat(logTester.logs(LoggerLevel.WARN)).contains("Storing measures on folders or modules is deprecated. Provided value of metric 'tests' is ignored.");
+ }
+
+}
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/scm/ScmMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/scm/ScmMediumIT.java
new file mode 100644
index 00000000000..04451bf92f0
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/scm/ScmMediumIT.java
@@ -0,0 +1,358 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.mediumtest.scm;
+
+import com.google.common.collect.ImmutableMap;
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.util.regex.Pattern;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.io.FileUtils;
+import org.assertj.core.util.Files;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.SonarEdition;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.scanner.mediumtest.ScannerMediumTester;
+import org.sonar.scanner.mediumtest.ScannerMediumTester.AnalysisBuilder;
+import org.sonar.scanner.protocol.output.FileStructure;
+import org.sonar.scanner.protocol.output.ScannerReport;
+import org.sonar.scanner.protocol.output.ScannerReport.Changesets.Changeset;
+import org.sonar.scanner.protocol.output.ScannerReport.Component;
+import org.sonar.scanner.protocol.output.ScannerReportReader;
+import org.sonar.scanner.repository.FileData;
+import org.sonar.xoo.XooPlugin;
+import org.sonar.xoo.rule.XooRulesDefinition;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ScmMediumIT {
+
+ 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 NO_BLAME_SCM_ON_SERVER_XOO = "src/no_blame_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";
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public LogTester logTester = new LogTester();
+
+ @Rule
+ public ScannerMediumTester tester = new ScannerMediumTester()
+ .setEdition(SonarEdition.COMMUNITY)
+ .registerPlugin("xoo", new XooPlugin())
+ .addDefaultQProfile("xoo", "Sonar Way")
+ .addRules(new XooRulesDefinition())
+ // active a rule just to be sure that xoo files are published
+ .addActiveRule("xoo", "xoo:OneIssuePerFile", null, "One Issue Per File", null, null, null)
+ .addFileData(CHANGED_CONTENT_SCM_ON_SERVER_XOO, new FileData(DigestUtils.md5Hex(SAMPLE_XOO_CONTENT), null))
+ .addFileData(SAME_CONTENT_NO_SCM_ON_SERVER_XOO, new FileData(DigestUtils.md5Hex(SAMPLE_XOO_CONTENT), null))
+ .addFileData(SAME_CONTENT_SCM_ON_SERVER_XOO, new FileData(DigestUtils.md5Hex(SAMPLE_XOO_CONTENT), "1.1"))
+ .addFileData(NO_BLAME_SCM_ON_SERVER_XOO, new FileData(DigestUtils.md5Hex(SAMPLE_XOO_CONTENT), "1.1"));
+
+ @Test
+ public void testScmMeasure() throws IOException, URISyntaxException {
+ File baseDir = prepareProject();
+
+ tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .put("sonar.scm.provider", "xoo")
+ .build())
+ .execute();
+
+ ScannerReport.Changesets fileScm = getChangesets(baseDir, "src/sample.xoo");
+
+ assertThat(fileScm.getChangesetIndexByLineList()).hasSize(5);
+
+ Changeset changesetLine1 = fileScm.getChangeset(fileScm.getChangesetIndexByLine(0));
+ assertThat(changesetLine1.getAuthor()).isEmpty();
+
+ Changeset changesetLine2 = fileScm.getChangeset(fileScm.getChangesetIndexByLine(1));
+ assertThat(changesetLine2.getAuthor()).isEqualTo(getNonAsciiAuthor().toLowerCase());
+
+ 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/scanner-report");
+ FileStructure fileStructure = new FileStructure(reportDir);
+ ScannerReportReader reader = new ScannerReportReader(fileStructure);
+
+ Component project = reader.readComponent(reader.readMetadata().getRootComponentRef());
+ for (Integer fileRef : project.getChildRefList()) {
+ Component file = reader.readComponent(fileRef);
+ if (file.getProjectRelativePath().equals(path)) {
+ return reader.readChangesets(file.getRef());
+ }
+ }
+ return null;
+ }
+
+ @Test
+ public void noScmOnEmptyFile() throws IOException, URISyntaxException {
+
+ File baseDir = prepareProject();
+
+ // Clear file content
+ FileUtils.write(new File(baseDir, "src/sample.xoo"), "");
+
+ tester.newAnalysis()
+ .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())
+ .execute();
+
+ ScannerReport.Changesets changesets = getChangesets(baseDir, "src/sample.xoo");
+
+ assertThat(changesets).isNull();
+ }
+
+ @Test
+ public void log_files_with_missing_blame() throws IOException, URISyntaxException {
+
+ File baseDir = prepareProject();
+ File xooFileWithoutBlame = new File(baseDir, "src/sample_no_blame.xoo");
+ FileUtils.write(xooFileWithoutBlame, "Sample xoo\ncontent\n3\n4\n5", StandardCharsets.UTF_8);
+
+ tester.newAnalysis()
+ .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())
+ .execute();
+
+ 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("SCM Publisher 2 source files to be analyzed", MISSING_BLAME_INFORMATION_FOR_THE_FOLLOWING_FILES,
+ " * src/sample_no_blame.xoo");
+
+ assertThat(logTester.logs().stream().anyMatch(s -> Pattern.matches("SCM Publisher 1/2 source file have been analyzed \\(done\\) \\| time=[0-9]+ms", s))).isTrue();
+ }
+
+ // SONAR-6397
+ @Test
+ public void optimize_blame() throws IOException, URISyntaxException {
+
+ File baseDir = prepareProject();
+ File changedContentScmOnServer = new File(baseDir, CHANGED_CONTENT_SCM_ON_SERVER_XOO);
+ FileUtils.write(changedContentScmOnServer, SAMPLE_XOO_CONTENT + "\nchanged", StandardCharsets.UTF_8);
+ 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",
+ StandardCharsets.UTF_8);
+
+ File sameContentScmOnServer = new File(baseDir, SAME_CONTENT_SCM_ON_SERVER_XOO);
+ FileUtils.write(sameContentScmOnServer, SAMPLE_XOO_CONTENT, StandardCharsets.UTF_8);
+ // No need to write .scm file since this file should not be blamed
+
+ File noBlameScmOnServer = new File(baseDir, NO_BLAME_SCM_ON_SERVER_XOO);
+ FileUtils.write(noBlameScmOnServer, SAMPLE_XOO_CONTENT + "\nchanged", StandardCharsets.UTF_8);
+ // No .scm file to emulate a failure during blame
+
+ File sameContentNoScmOnServer = new File(baseDir, SAME_CONTENT_NO_SCM_ON_SERVER_XOO);
+ FileUtils.write(sameContentNoScmOnServer, SAMPLE_XOO_CONTENT, StandardCharsets.UTF_8);
+ 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",
+ StandardCharsets.UTF_8);
+
+ tester.newAnalysis()
+ .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())
+ .execute();
+
+ assertThat(getChangesets(baseDir, "src/sample.xoo")).isNotNull();
+
+ assertThat(getChangesets(baseDir, CHANGED_CONTENT_SCM_ON_SERVER_XOO).getCopyFromPrevious()).isFalse();
+
+ assertThat(getChangesets(baseDir, SAME_CONTENT_SCM_ON_SERVER_XOO).getCopyFromPrevious()).isTrue();
+
+ assertThat(getChangesets(baseDir, SAME_CONTENT_NO_SCM_ON_SERVER_XOO).getCopyFromPrevious()).isFalse();
+
+ assertThat(getChangesets(baseDir, NO_BLAME_SCM_ON_SERVER_XOO)).isNull();
+
+ // 5 .xoo files + 3 .scm files, but only 4 marked for publishing. 1 file is SAME so not included in the total
+ assertThat(logTester.logs()).containsSubsequence("8 files indexed");
+ assertThat(logTester.logs()).containsSubsequence("SCM Publisher 4 source files to be analyzed");
+ assertThat(logTester.logs().stream().anyMatch(s -> Pattern.matches("SCM Publisher 3/4 source files have been analyzed \\(done\\) \\| time=[0-9]+ms", s))).isTrue();
+ assertThat(logTester.logs()).containsSubsequence(MISSING_BLAME_INFORMATION_FOR_THE_FOLLOWING_FILES, " * src/no_blame_scm_on_server.xoo");
+ }
+
+ @Test
+ public void forceReload() throws IOException, URISyntaxException {
+
+ File baseDir = prepareProject();
+ File xooFileNoScm = new File(baseDir, SAME_CONTENT_SCM_ON_SERVER_XOO);
+ FileUtils.write(xooFileNoScm, SAMPLE_XOO_CONTENT, StandardCharsets.UTF_8);
+ 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",
+ StandardCharsets.UTF_8);
+
+ AnalysisBuilder analysisBuilder = tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .put("sonar.scm.provider", "xoo")
+ // Force reload
+ .put("sonar.scm.forceReloadAll", "true")
+ .build());
+
+ analysisBuilder.execute();
+
+ 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, URISyntaxException {
+
+ File baseDir = prepareProject();
+
+ tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .put("sonar.links.scm_dev", "scm:xoo:foobar")
+ .build())
+ .execute();
+
+ ScannerReport.Changesets file1Scm = getChangesets(baseDir, "src/sample.xoo");
+ assertThat(file1Scm).isNotNull();
+ }
+
+ @Test
+ public void testAutoDetection() throws IOException, URISyntaxException {
+
+ File baseDir = prepareProject();
+ new File(baseDir, ".xoo").createNewFile();
+
+ tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .build())
+ .execute();
+
+ ScannerReport.Changesets file1Scm = getChangesets(baseDir, "src/sample.xoo");
+ assertThat(file1Scm).isNotNull();
+ }
+
+ private String getNonAsciiAuthor() {
+ return Files.contentOf(new File("test-resources/mediumtest/blameAuthor.txt"), StandardCharsets.UTF_8);
+
+ }
+
+ 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", StandardCharsets.UTF_8);
+ File xooScmFile1 = new File(srcDir, "sample.xoo.scm");
+ FileUtils.write(xooScmFile1,
+ // revision,author,dateTime
+ "1,,2013-01-04\n" +
+ "2," + getNonAsciiAuthor() + ",2013-01-04\n" +
+ "3,julien,2013-02-03\n" +
+ "3,julien,2013-02-03\n" +
+ "4,simon,2013-03-04\n",
+ StandardCharsets.UTF_8);
+
+ return baseDir;
+ }
+
+ @Test
+ public void testDisableScmSensor() throws IOException, URISyntaxException {
+
+ File baseDir = prepareProject();
+
+ tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.sources", "src")
+ .put("sonar.scm.disabled", "true")
+ .put("sonar.scm.provider", "xoo")
+ .put("sonar.cpd.xoo.skip", "true")
+ .build())
+ .execute();
+
+ ScannerReport.Changesets changesets = getChangesets(baseDir, "src/sample.xoo");
+ assertThat(changesets).isNull();
+ }
+
+}
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/symbol/SymbolMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/symbol/SymbolMediumIT.java
new file mode 100644
index 00000000000..f6f535ac1d7
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/symbol/SymbolMediumIT.java
@@ -0,0 +1,105 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.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.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.scanner.mediumtest.AnalysisResult;
+import org.sonar.scanner.mediumtest.ScannerMediumTester;
+import org.sonar.scanner.protocol.output.ScannerReport;
+import org.sonar.xoo.XooPlugin;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SymbolMediumIT {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public ScannerMediumTester tester = new ScannerMediumTester()
+ .registerPlugin("xoo", new XooPlugin())
+ .addDefaultQProfile("xoo", "Sonar Way");
+
+ @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, "1:7:1:10,3:8:3:11");
+
+ AnalysisResult result = tester.newAnalysis()
+ .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())
+ .execute();
+
+ 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, "1:7:1:10,3:8:4:1");
+
+ AnalysisResult result = tester.newAnalysis()
+ .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())
+ .execute();
+
+ 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/it/java/org/sonar/scanner/mediumtest/tasks/TasksMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/tasks/TasksMediumIT.java
new file mode 100644
index 00000000000..776a11575bf
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/tasks/TasksMediumIT.java
@@ -0,0 +1,75 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.mediumtest.tasks;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.Plugin;
+import org.sonar.api.utils.MessageException;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.scanner.mediumtest.ScannerMediumTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+
+public class TasksMediumIT {
+
+ @Rule
+ public LogTester logTester = new LogTester();
+
+ @Rule
+ public ScannerMediumTester tester = new ScannerMediumTester()
+ .registerPlugin("faketask", new FakePlugin());
+
+ @Test
+ public void failWhenCallingTask() {
+ try {
+ tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.task", "fake").build())
+ .execute();
+ fail("Expected exception");
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(MessageException.class).hasMessage("Tasks support was removed in SonarQube 7.6.");
+ }
+ }
+
+ @Test
+ public void failWhenCallingViews() {
+ try {
+ tester.newAnalysis()
+ .properties(ImmutableMap.<String, String>builder()
+ .put("sonar.task", "views").build())
+ .execute();
+ fail("Expected exception");
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(MessageException.class).hasMessage("The task 'views' was removed with SonarQube 7.1. You can safely remove this call since portfolios and applications are automatically re-calculated.");
+ }
+ }
+
+ private static class FakePlugin implements Plugin {
+
+ @Override
+ public void define(Context context) {
+ }
+ }
+
+}
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/tests/GenericTestExecutionMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/tests/GenericTestExecutionMediumIT.java
new file mode 100644
index 00000000000..86962f74824
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/tests/GenericTestExecutionMediumIT.java
@@ -0,0 +1,90 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.mediumtest.tests;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.scanner.mediumtest.AnalysisResult;
+import org.sonar.scanner.mediumtest.ScannerMediumTester;
+import org.sonar.xoo.XooPlugin;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+
+public class GenericTestExecutionMediumIT {
+ private final List<String> logs = new ArrayList<>();
+
+ @Rule
+ public ScannerMediumTester tester = new ScannerMediumTester()
+ .registerPlugin("xoo", new XooPlugin())
+ .addDefaultQProfile("xoo", "Sonar Way");
+
+ @Test
+ public void singleReport() {
+
+ File projectDir = new File("test-resources/mediumtest/xoo/sample-generic-test-exec");
+
+ AnalysisResult result = tester
+ .setLogOutput((msg, level) -> logs.add(msg))
+ .newAnalysis(new File(projectDir, "sonar-project.properties"))
+ .property("sonar.testExecutionReportPaths", "unittest.xml")
+ .execute();
+
+ InputFile testFile = result.inputFile("testx/ClassOneTest.xoo");
+ assertThat(result.allMeasures().get(testFile.key())).extracting("metricKey", "intValue.value", "longValue.value")
+ .containsOnly(
+ tuple(CoreMetrics.TESTS_KEY, 3, 0L),
+ tuple(CoreMetrics.SKIPPED_TESTS_KEY, 1, 0L),
+ tuple(CoreMetrics.TEST_ERRORS_KEY, 1, 0L),
+ tuple(CoreMetrics.TEST_EXECUTION_TIME_KEY, 0, 1105L),
+ tuple(CoreMetrics.TEST_FAILURES_KEY, 1, 0L));
+
+ assertThat(logs).noneMatch(l -> l.contains("Please use 'sonar.testExecutionReportPaths'"));
+ }
+
+ @Test
+ public void twoReports() {
+
+ File projectDir = new File("test-resources/mediumtest/xoo/sample-generic-test-exec");
+
+ AnalysisResult result = tester
+ .setLogOutput((msg, level) -> logs.add(msg))
+ .newAnalysis(new File(projectDir, "sonar-project.properties"))
+ .property("sonar.testExecutionReportPaths", "unittest.xml,unittest2.xml")
+ .execute();
+
+ InputFile testFile = result.inputFile("testx/ClassOneTest.xoo");
+ assertThat(result.allMeasures().get(testFile.key())).extracting("metricKey", "intValue.value", "longValue.value")
+ .containsOnly(
+ tuple(CoreMetrics.TESTS_KEY, 4, 0L),
+ tuple(CoreMetrics.SKIPPED_TESTS_KEY, 2, 0L),
+ tuple(CoreMetrics.TEST_ERRORS_KEY, 1, 0L),
+ tuple(CoreMetrics.TEST_EXECUTION_TIME_KEY, 0, 1610L),
+ tuple(CoreMetrics.TEST_FAILURES_KEY, 1, 0L));
+
+ assertThat(logs).noneMatch(l -> l.contains("Please use 'sonar.testExecutionReportPaths'"));
+ }
+
+}
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scm/svn/SvnBlameCommandIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scm/svn/SvnBlameCommandIT.java
new file mode 100644
index 00000000000..dec6960b3cb
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scm/svn/SvnBlameCommandIT.java
@@ -0,0 +1,391 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scm.svn;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.stream.IntStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.mockito.ArgumentCaptor;
+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.TestInputFileBuilder;
+import org.sonar.api.batch.scm.BlameCommand.BlameInput;
+import org.sonar.api.batch.scm.BlameCommand.BlameOutput;
+import org.sonar.api.batch.scm.BlameLine;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.tmatesoft.svn.core.SVNAuthenticationException;
+import org.tmatesoft.svn.core.SVNDepth;
+import org.tmatesoft.svn.core.SVNURL;
+import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
+import org.tmatesoft.svn.core.internal.wc2.compat.SvnCodec;
+import org.tmatesoft.svn.core.wc.ISVNOptions;
+import org.tmatesoft.svn.core.wc.SVNClientManager;
+import org.tmatesoft.svn.core.wc.SVNLogClient;
+import org.tmatesoft.svn.core.wc.SVNRevision;
+import org.tmatesoft.svn.core.wc.SVNStatus;
+import org.tmatesoft.svn.core.wc.SVNStatusClient;
+import org.tmatesoft.svn.core.wc.SVNStatusType;
+import org.tmatesoft.svn.core.wc.SVNUpdateClient;
+import org.tmatesoft.svn.core.wc.SVNWCUtil;
+import org.tmatesoft.svn.core.wc2.SvnCheckout;
+import org.tmatesoft.svn.core.wc2.SvnTarget;
+
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+@RunWith(Parameterized.class)
+public class SvnBlameCommandIT {
+
+ /*
+ * Note about SONARSCSVN-11: The case of a project baseDir is in a subFolder of working copy is part of method tests by default
+ */
+
+ private static final String DUMMY_JAVA = "src/main/java/org/dummy/Dummy.java";
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public LogTester logTester = new LogTester();
+
+ private FileSystem fs;
+ private BlameInput input;
+ private String serverVersion;
+ private int wcVersion;
+
+ @Parameters(name = "SVN server version {0}, WC version {1}")
+ public static Iterable<Object[]> data() {
+ return Arrays.asList(new Object[][] {{"1.6", 10}, {"1.7", 29}, {"1.8", 31}, {"1.9", 31}});
+ }
+
+ public SvnBlameCommandIT(String serverVersion, int wcVersion) {
+ this.serverVersion = serverVersion;
+ this.wcVersion = wcVersion;
+ }
+
+ @Before
+ public void prepare() {
+ fs = mock(FileSystem.class);
+ input = mock(BlameInput.class);
+ when(input.fileSystem()).thenReturn(fs);
+ }
+
+ @Test
+ public void testParsingOfOutput() throws Exception {
+ File repoDir = unzip("repo-svn.zip");
+
+ String scmUrl = "file:///" + unixPath(new File(repoDir, "repo-svn"));
+ File baseDir = new File(checkout(scmUrl), "dummy-svn");
+
+ when(fs.baseDir()).thenReturn(baseDir);
+ DefaultInputFile inputFile = new TestInputFileBuilder("foo", DUMMY_JAVA)
+ .setLines(27)
+ .setModuleBaseDir(baseDir.toPath())
+ .build();
+
+ BlameOutput blameResult = mock(BlameOutput.class);
+ when(input.filesToBlame()).thenReturn(singletonList(inputFile));
+
+ newSvnBlameCommand().blame(input, blameResult);
+ ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class);
+ verify(blameResult).blameResult(eq(inputFile), captor.capture());
+ List<BlameLine> result = captor.getValue();
+ assertThat(result).hasSize(27);
+ Date commitDate = new Date(1342691097393L);
+ BlameLine[] expected = IntStream.rangeClosed(1, 27).mapToObj(i -> new BlameLine().date(commitDate).revision("2").author("dgageot")).toArray(BlameLine[]::new);
+ assertThat(result).containsExactly(expected);
+ }
+
+ private File unzip(String repoName) throws IOException {
+ File repoDir = temp.newFolder();
+ try {
+ javaUnzip(Paths.get(this.getClass().getResource("test-repos").toURI()).resolve(serverVersion).resolve(repoName).toFile(), repoDir);
+ return repoDir;
+ } catch (URISyntaxException e) {
+ throw new IOException(e);
+ }
+ }
+
+ private File checkout(String scmUrl) throws Exception {
+ ISVNOptions options = SVNWCUtil.createDefaultOptions(true);
+ ISVNAuthenticationManager isvnAuthenticationManager = SVNWCUtil.createDefaultAuthenticationManager(null, null, (char[]) null, false);
+ SVNClientManager svnClientManager = SVNClientManager.newInstance(options, isvnAuthenticationManager);
+ File out = temp.newFolder();
+ SVNUpdateClient updateClient = svnClientManager.getUpdateClient();
+ SvnCheckout co = updateClient.getOperationsFactory().createCheckout();
+ co.setUpdateLocksOnDemand(updateClient.isUpdateLocksOnDemand());
+ co.setSource(SvnTarget.fromURL(SVNURL.parseURIEncoded(scmUrl), SVNRevision.HEAD));
+ co.setSingleTarget(SvnTarget.fromFile(out));
+ co.setRevision(SVNRevision.HEAD);
+ co.setDepth(SVNDepth.INFINITY);
+ co.setAllowUnversionedObstructions(false);
+ co.setIgnoreExternals(updateClient.isIgnoreExternals());
+ co.setExternalsHandler(SvnCodec.externalsHandler(updateClient.getExternalsHandler()));
+ co.setTargetWorkingCopyFormat(wcVersion);
+ co.run();
+ return out;
+ }
+
+ @Test
+ public void testParsingOfOutputWithMergeHistory() throws Exception {
+ File repoDir = unzip("repo-svn-with-merge.zip");
+
+ String scmUrl = "file:///" + unixPath(new File(repoDir, "repo-svn"));
+ File baseDir = new File(checkout(scmUrl), "dummy-svn/trunk");
+
+ when(fs.baseDir()).thenReturn(baseDir);
+ DefaultInputFile inputFile = new TestInputFileBuilder("foo", DUMMY_JAVA)
+ .setLines(27)
+ .setModuleBaseDir(baseDir.toPath())
+ .build();
+
+ BlameOutput blameResult = mock(BlameOutput.class);
+ when(input.filesToBlame()).thenReturn(singletonList(inputFile));
+
+ newSvnBlameCommand().blame(input, blameResult);
+ ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class);
+ verify(blameResult).blameResult(eq(inputFile), captor.capture());
+ List<BlameLine> result = captor.getValue();
+ assertThat(result).hasSize(27);
+ Date commitDate = new Date(1342691097393L);
+ Date revision6Date = new Date(1415262184300L);
+
+ BlameLine[] expected = IntStream.rangeClosed(1, 27).mapToObj(i -> {
+ if (i == 2 || i == 24) {
+ return new BlameLine().date(revision6Date).revision("6").author("henryju");
+ } else {
+ return new BlameLine().date(commitDate).revision("2").author("dgageot");
+ }
+ }).toArray(BlameLine[]::new);
+
+ assertThat(result).containsExactly(expected);
+ }
+
+ @Test
+ public void shouldNotFailIfFileContainsLocalModification() throws Exception {
+ File repoDir = unzip("repo-svn.zip");
+
+ String scmUrl = "file:///" + unixPath(new File(repoDir, "repo-svn"));
+ File baseDir = new File(checkout(scmUrl), "dummy-svn");
+
+ when(fs.baseDir()).thenReturn(baseDir);
+ DefaultInputFile inputFile = new TestInputFileBuilder("foo", DUMMY_JAVA)
+ .setLines(28)
+ .setModuleBaseDir(baseDir.toPath())
+ .build();
+
+ Files.write(baseDir.toPath().resolve(DUMMY_JAVA), "\n//foo".getBytes(), StandardOpenOption.APPEND);
+
+ BlameOutput blameResult = mock(BlameOutput.class);
+ when(input.filesToBlame()).thenReturn(singletonList(inputFile));
+
+ newSvnBlameCommand().blame(input, blameResult);
+ verifyNoInteractions(blameResult);
+ }
+
+ // SONARSCSVN-7
+ @Test
+ public void shouldNotFailOnWrongFilename() throws Exception {
+ File repoDir = unzip("repo-svn.zip");
+
+ String scmUrl = "file:///" + unixPath(new File(repoDir, "repo-svn"));
+ File baseDir = new File(checkout(scmUrl), "dummy-svn");
+
+ when(fs.baseDir()).thenReturn(baseDir);
+ DefaultInputFile inputFile = new TestInputFileBuilder("foo", DUMMY_JAVA.toLowerCase())
+ .setLines(27)
+ .setModuleBaseDir(baseDir.toPath())
+ .build();
+
+ BlameOutput blameResult = mock(BlameOutput.class);
+ when(input.filesToBlame()).thenReturn(singletonList(inputFile));
+
+ newSvnBlameCommand().blame(input, blameResult);
+ verifyNoInteractions(blameResult);
+ }
+
+ @Test
+ public void shouldNotFailOnUncommitedFile() throws Exception {
+ File repoDir = unzip("repo-svn.zip");
+
+ String scmUrl = "file:///" + unixPath(new File(repoDir, "repo-svn"));
+ File baseDir = new File(checkout(scmUrl), "dummy-svn");
+
+ when(fs.baseDir()).thenReturn(baseDir);
+ String relativePath = "src/main/java/org/dummy/Dummy2.java";
+ DefaultInputFile inputFile = new TestInputFileBuilder("foo", relativePath)
+ .setLines(28)
+ .setModuleBaseDir(baseDir.toPath())
+ .build();
+
+ Files.write(baseDir.toPath().resolve(relativePath), "package org.dummy;\npublic class Dummy2 {}".getBytes());
+
+ BlameOutput blameResult = mock(BlameOutput.class);
+ when(input.filesToBlame()).thenReturn(singletonList(inputFile));
+
+ newSvnBlameCommand().blame(input, blameResult);
+ verifyNoInteractions(blameResult);
+ }
+
+ @Test
+ public void shouldNotFailOnUncommitedDir() throws Exception {
+ File repoDir = unzip("repo-svn.zip");
+
+ String scmUrl = "file:///" + unixPath(new File(repoDir, "repo-svn"));
+ File baseDir = new File(checkout(scmUrl), "dummy-svn");
+
+ when(fs.baseDir()).thenReturn(baseDir);
+ String relativePath = "src/main/java/org/dummy2/dummy/Dummy.java";
+ DefaultInputFile inputFile = new TestInputFileBuilder("foo", relativePath)
+ .setLines(28)
+ .setModuleBaseDir(baseDir.toPath())
+ .build();
+
+ Path filepath = new File(baseDir, relativePath).toPath();
+ Files.createDirectories(filepath.getParent());
+ Files.write(filepath, "package org.dummy;\npublic class Dummy {}".getBytes());
+
+ BlameOutput blameResult = mock(BlameOutput.class);
+ when(input.filesToBlame()).thenReturn(singletonList(inputFile));
+
+ newSvnBlameCommand().blame(input, blameResult);
+ verifyNoInteractions(blameResult);
+ }
+
+ @Test
+ public void blame_givenNoCredentials_logWarning() throws Exception {
+ BlameOutput output = mock(BlameOutput.class);
+ InputFile inputFile = mock(InputFile.class);
+ SvnBlameCommand svnBlameCommand = newSvnBlameCommand();
+
+ SVNClientManager clientManager = mock(SVNClientManager.class);
+ SVNLogClient logClient = mock(SVNLogClient.class);
+ SVNStatusClient statusClient = mock(SVNStatusClient.class);
+ SVNStatus status = mock(SVNStatus.class);
+
+ when(clientManager.getLogClient()).thenReturn(logClient);
+ when(clientManager.getStatusClient()).thenReturn(statusClient);
+ when(status.getContentsStatus()).thenReturn(SVNStatusType.STATUS_NORMAL);
+ when(inputFile.file()).thenReturn(mock(File.class));
+ when(statusClient.doStatus(any(File.class), anyBoolean())).thenReturn(status);
+
+ doThrow(SVNAuthenticationException.class).when(logClient).doAnnotate(any(File.class), any(SVNRevision.class),
+ any(SVNRevision.class), any(SVNRevision.class), anyBoolean(), anyBoolean(), any(AnnotationHandler.class),
+ eq(null));
+
+ assertThrows(IllegalStateException.class, () -> {
+ svnBlameCommand.blame(clientManager, inputFile, output);
+ assertThat(logTester.logs(LoggerLevel.WARN)).contains("Authentication to SVN server is required but no " +
+ "authentication data was passed to the scanner");
+ });
+
+ }
+
+ @Test
+ public void blame_givenCredentialsSupplied_doNotlogWarning() throws Exception {
+ BlameOutput output = mock(BlameOutput.class);
+ InputFile inputFile = mock(InputFile.class);
+ SvnConfiguration properties = mock(SvnConfiguration.class);
+ SvnBlameCommand svnBlameCommand = new SvnBlameCommand(properties);
+
+ SVNClientManager clientManager = mock(SVNClientManager.class);
+ SVNLogClient logClient = mock(SVNLogClient.class);
+ SVNStatusClient statusClient = mock(SVNStatusClient.class);
+ SVNStatus status = mock(SVNStatus.class);
+
+ when(properties.isEmpty()).thenReturn(true);
+ when(clientManager.getLogClient()).thenReturn(logClient);
+ when(clientManager.getStatusClient()).thenReturn(statusClient);
+ when(status.getContentsStatus()).thenReturn(SVNStatusType.STATUS_NORMAL);
+ when(inputFile.file()).thenReturn(mock(File.class));
+ when(statusClient.doStatus(any(File.class), anyBoolean())).thenReturn(status);
+
+ doThrow(SVNAuthenticationException.class).when(logClient).doAnnotate(any(File.class), any(SVNRevision.class),
+ any(SVNRevision.class), any(SVNRevision.class), anyBoolean(), anyBoolean(), any(AnnotationHandler.class),
+ eq(null));
+
+ assertThrows(IllegalStateException.class, () -> {
+ svnBlameCommand.blame(clientManager, inputFile, output);
+ assertThat(logTester.logs(LoggerLevel.WARN)).isEmpty();
+ });
+
+ }
+
+ private static void javaUnzip(File zip, File toDir) {
+ try {
+ try (ZipFile zipFile = new ZipFile(zip)) {
+ Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ File to = new File(toDir, entry.getName());
+ if (entry.isDirectory()) {
+ Files.createDirectories(to.toPath());
+ } else {
+ File parent = to.getParentFile();
+ if (parent != null) {
+ Files.createDirectories(parent.toPath());
+ }
+
+ Files.copy(zipFile.getInputStream(entry), to.toPath());
+ }
+ }
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to unzip " + zip + " to " + toDir, e);
+ }
+ }
+
+ private static String unixPath(File file) {
+ return file.getAbsolutePath().replace('\\', '/');
+ }
+
+ private SvnBlameCommand newSvnBlameCommand() {
+ return new SvnBlameCommand(mock(SvnConfiguration.class));
+ }
+}
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scm/svn/SvnTester.java b/sonar-scanner-engine/src/it/java/org/sonar/scm/svn/SvnTester.java
new file mode 100644
index 00000000000..c24f592c064
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scm/svn/SvnTester.java
@@ -0,0 +1,129 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scm.svn;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import org.tmatesoft.svn.core.SVNDepth;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNURL;
+import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
+import org.tmatesoft.svn.core.wc.SVNClientManager;
+import org.tmatesoft.svn.core.wc.SVNCopyClient;
+import org.tmatesoft.svn.core.wc.SVNCopySource;
+import org.tmatesoft.svn.core.wc.SVNRevision;
+import org.tmatesoft.svn.core.wc.SVNUpdateClient;
+import org.tmatesoft.svn.core.wc2.SvnList;
+import org.tmatesoft.svn.core.wc2.SvnOperationFactory;
+import org.tmatesoft.svn.core.wc2.SvnRemoteMkDir;
+import org.tmatesoft.svn.core.wc2.SvnTarget;
+
+public class SvnTester {
+ private final SVNClientManager manager = SVNClientManager.newInstance(new SvnOperationFactory());
+
+ private final SVNURL localRepository;
+
+ public SvnTester(Path root) throws SVNException, IOException {
+ localRepository = SVNRepositoryFactory.createLocalRepository(root.toFile(), false, false);
+ mkdir("trunk");
+ mkdir("branches");
+ }
+
+ private void mkdir(String relpath) throws IOException, SVNException {
+ SvnRemoteMkDir remoteMkDir = manager.getOperationFactory().createRemoteMkDir();
+ remoteMkDir.addTarget(SvnTarget.fromURL(localRepository.appendPath(relpath, false)));
+ remoteMkDir.run();
+ }
+
+ public void createBranch(String branchName) throws IOException, SVNException {
+ SVNCopyClient copyClient = manager.getCopyClient();
+ SVNCopySource source = new SVNCopySource(SVNRevision.HEAD, SVNRevision.HEAD, localRepository.appendPath("trunk", false));
+ copyClient.doCopy(new SVNCopySource[] {source}, localRepository.appendPath("branches/" + branchName, false), false, false, true, "Create branch", null);
+ }
+
+ public void checkout(Path worktree, String path) throws SVNException {
+ SVNUpdateClient updateClient = manager.getUpdateClient();
+ updateClient.doCheckout(localRepository.appendPath(path, false),
+ worktree.toFile(), null, null, SVNDepth.INFINITY, false);
+ }
+
+ public void add(Path worktree, String filename) throws SVNException {
+ manager.getWCClient().doAdd(worktree.resolve(filename).toFile(), true, false, false, SVNDepth.INFINITY, false, false, true);
+ }
+
+ public void copy(Path worktree, String src, String dst) throws SVNException {
+ SVNCopyClient copyClient = manager.getCopyClient();
+ SVNCopySource source = new SVNCopySource(SVNRevision.HEAD, SVNRevision.HEAD, worktree.resolve(src).toFile());
+ copyClient.doCopy(new SVNCopySource[]{source}, worktree.resolve(dst).toFile(), false, false, true);
+ }
+
+ public void commit(Path worktree) throws SVNException {
+ manager.getCommitClient().doCommit(new File[] {worktree.toFile()}, false, "commit " + worktree, null, null, false, false, SVNDepth.INFINITY);
+ }
+
+ public void update(Path worktree) throws SVNException {
+ manager.getUpdateClient().doUpdate(new File[] {worktree.toFile()}, SVNRevision.HEAD, SVNDepth.INFINITY, false, false);
+ }
+
+ public Collection<String> list(String... paths) throws SVNException {
+ Set<String> results = new HashSet<>();
+
+ SvnList list = manager.getOperationFactory().createList();
+ if (paths.length == 0) {
+ list.addTarget(SvnTarget.fromURL(localRepository));
+ } else {
+ for (String path : paths) {
+ list.addTarget(SvnTarget.fromURL(localRepository.appendPath(path, false)));
+ }
+ }
+ list.setDepth(SVNDepth.INFINITY);
+ list.setReceiver((svnTarget, svnDirEntry) -> {
+ String path = svnDirEntry.getRelativePath();
+ if (!path.isEmpty()) {
+ results.add(path);
+ }
+ });
+ list.run();
+
+ return results;
+ }
+
+ public void createFile(Path worktree, String filename, String content) throws IOException {
+ Files.write(worktree.resolve(filename), content.getBytes());
+ }
+
+ public void createFile(Path worktree, String filename) throws IOException {
+ createFile(worktree, filename, filename + "\n");
+ }
+
+ public void appendToFile(Path worktree, String filename) throws IOException {
+ Files.write(worktree.resolve(filename), (filename + "\n").getBytes(), StandardOpenOption.APPEND);
+ }
+
+ public void deleteFile(Path worktree, String filename) throws SVNException {
+ manager.getWCClient().doDelete(worktree.resolve(filename).toFile(), false, false);
+ }
+}
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scm/svn/SvnTesterIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scm/svn/SvnTesterIT.java
new file mode 100644
index 00000000000..a285a63cace
--- /dev/null
+++ b/sonar-scanner-engine/src/it/java/org/sonar/scm/svn/SvnTesterIT.java
@@ -0,0 +1,68 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scm.svn;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.tmatesoft.svn.core.SVNException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SvnTesterIT {
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ private SvnTester tester;
+
+ @Before
+ public void before() throws IOException, SVNException {
+ tester = new SvnTester(temp.newFolder().toPath());
+ }
+
+ @Test
+ public void test_init() throws SVNException {
+ assertThat(tester.list()).containsExactlyInAnyOrder("trunk", "branches");
+ }
+
+ @Test
+ public void test_add_and_commit() throws IOException, SVNException {
+ assertThat(tester.list("trunk")).isEmpty();
+
+ Path worktree = temp.newFolder().toPath();
+ tester.checkout(worktree, "trunk");
+ tester.createFile(worktree, "file1");
+
+ tester.add(worktree, "file1");
+ tester.commit(worktree);
+
+ assertThat(tester.list("trunk")).containsOnly("file1");
+ }
+
+ @Test
+ public void test_createBranch() throws IOException, SVNException {
+ tester.createBranch("b1");
+ assertThat(tester.list()).containsExactlyInAnyOrder("trunk", "branches", "branches/b1");
+ assertThat(tester.list("branches")).containsOnly("b1");
+ }
+}
diff --git a/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn-with-merge.zip b/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn-with-merge.zip
new file mode 100644
index 00000000000..d3c3ee5fa1e
--- /dev/null
+++ b/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn-with-merge.zip
Binary files differ
diff --git a/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn.zip b/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn.zip
new file mode 100644
index 00000000000..291d4fb86f7
--- /dev/null
+++ b/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn.zip
Binary files differ
diff --git a/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn-with-merge.zip b/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn-with-merge.zip
new file mode 100644
index 00000000000..d3c3ee5fa1e
--- /dev/null
+++ b/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn-with-merge.zip
Binary files differ
diff --git a/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn.zip b/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn.zip
new file mode 100644
index 00000000000..291d4fb86f7
--- /dev/null
+++ b/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn.zip
Binary files differ
diff --git a/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn-with-merge.zip b/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn-with-merge.zip
new file mode 100644
index 00000000000..852a05e4574
--- /dev/null
+++ b/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn-with-merge.zip
Binary files differ
diff --git a/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn.zip b/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn.zip
new file mode 100644
index 00000000000..f31352c5e03
--- /dev/null
+++ b/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn.zip
Binary files differ
diff --git a/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn-with-merge.zip b/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn-with-merge.zip
new file mode 100644
index 00000000000..ca672fd6681
--- /dev/null
+++ b/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn-with-merge.zip
Binary files differ
diff --git a/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn.zip b/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn.zip
new file mode 100644
index 00000000000..39b241cd698
--- /dev/null
+++ b/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn.zip
Binary files differ