]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18679 moved unit test to integration test in sonar-scanner-engine
authorLukasz Jarocki <lukasz.jarocki@sonarsource.com>
Fri, 17 Mar 2023 09:05:42 +0000 (10:05 +0100)
committerLukasz Jarocki <lukasz.jarocki@sonarsource.com>
Fri, 17 Mar 2023 09:45:58 +0000 (10:45 +0100)
78 files changed:
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/branch/BranchMediumIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/branch/DeprecatedBranchMediumIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/coverage/CoverageMediumIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/coverage/GenericCoverageMediumIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/cpd/CpdMediumIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/fs/FileSystemMediumIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/fs/NoLanguagesPluginsMediumIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/fs/ProjectBuilderMediumIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/highlighting/HighlightingMediumIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/ChecksMediumIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/ExternalIssuesMediumIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/IssuesMediumIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/IssuesOnDirMediumIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/IssuesOnModuleMediumIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/MultilineIssuesMediumIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/PreviewMediumIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/log/ExceptionHandlingMediumIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/log/LogListenerIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/measures/MeasuresMediumIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/scm/ScmMediumIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/symbol/SymbolMediumIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/tasks/TasksMediumIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/tests/GenericTestExecutionMediumIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scm/svn/SvnBlameCommandIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scm/svn/SvnTester.java [new file with mode: 0644]
sonar-scanner-engine/src/it/java/org/sonar/scm/svn/SvnTesterIT.java [new file with mode: 0644]
sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn-with-merge.zip [new file with mode: 0644]
sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn.zip [new file with mode: 0644]
sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn-with-merge.zip [new file with mode: 0644]
sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn.zip [new file with mode: 0644]
sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn-with-merge.zip [new file with mode: 0644]
sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn.zip [new file with mode: 0644]
sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn-with-merge.zip [new file with mode: 0644]
sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn.zip [new file with mode: 0644]
sonar-scanner-engine/src/test/java/org/sonar/scanner/cache/WriteCacheImplTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/LogOutputRecorder.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/branch/BranchMediumTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/branch/DeprecatedBranchMediumTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/coverage/CoverageMediumTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/coverage/GenericCoverageMediumTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/cpd/CpdMediumTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/fs/FileSystemMediumTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/fs/NoLanguagesPluginsMediumTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/fs/ProjectBuilderMediumTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/highlighting/HighlightingMediumTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/ChecksMediumTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/ExternalIssuesMediumTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/IssuesMediumTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/IssuesOnDirMediumTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/IssuesOnModuleMediumTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/MultilineIssuesMediumTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/PreviewMediumTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/log/ExceptionHandlingMediumTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/log/LogListenerTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/measures/MeasuresMediumTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/scm/ScmMediumTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/symbol/SymbolMediumTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/tasks/TasksMediumTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/tests/GenericTestExecutionMediumTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/report/JavaArchitectureInformationProviderTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/DefaultQualityProfileLoaderTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/SingleProjectRepositoryTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/LanguageDetectionTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/ModuleSensorContextTest.java
sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnBlameCommandTest.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnScmSupportTest.java
sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnTester.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnTesterTest.java [deleted file]
sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn-with-merge.zip [deleted file]
sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn.zip [deleted file]
sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn-with-merge.zip [deleted file]
sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn.zip [deleted file]
sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn-with-merge.zip [deleted file]
sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn.zip [deleted file]
sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn-with-merge.zip [deleted file]
sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn.zip [deleted file]

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 (file)
index 0000000..a7a181a
--- /dev/null
@@ -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 (file)
index 0000000..02f68d9
--- /dev/null
@@ -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 (file)
index 0000000..a6453f2
--- /dev/null
@@ -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 (file)
index 0000000..08faf0f
--- /dev/null
@@ -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 (file)
index 0000000..1f16e72
--- /dev/null
@@ -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 (file)
index 0000000..0975bb7
--- /dev/null
@@ -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 (file)
index 0000000..9c1c553
--- /dev/null
@@ -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 (file)
index 0000000..a32056a
--- /dev/null
@@ -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 (file)
index 0000000..d7e75c2
--- /dev/null
@@ -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 (file)
index 0000000..811c883
--- /dev/null
@@ -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 (file)
index 0000000..30db617
--- /dev/null
@@ -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 (file)
index 0000000..2f9d939
--- /dev/null
@@ -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 (file)
index 0000000..feaf921
--- /dev/null
@@ -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 (file)
index 0000000..8055337
--- /dev/null
@@ -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 (file)
index 0000000..b83481d
--- /dev/null
@@ -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 (file)
index 0000000..5d69b83
--- /dev/null
@@ -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 (file)
index 0000000..6cd8179
--- /dev/null
@@ -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 (file)
index 0000000..64c4125
--- /dev/null
@@ -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 (file)
index 0000000..e2a63e2
--- /dev/null
@@ -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 (file)
index 0000000..c3f4fb1
--- /dev/null
@@ -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 (file)
index 0000000..04451bf
--- /dev/null
@@ -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 (file)
index 0000000..f6f535a
--- /dev/null
@@ -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 (file)
index 0000000..776a115
--- /dev/null
@@ -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 (file)
index 0000000..86962f7
--- /dev/null
@@ -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 (file)
index 0000000..dec6960
--- /dev/null
@@ -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 (file)
index 0000000..c24f592
--- /dev/null
@@ -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 (file)
index 0000000..a285a63
--- /dev/null
@@ -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 (file)
index 0000000..d3c3ee5
Binary files /dev/null and b/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn-with-merge.zip 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 (file)
index 0000000..291d4fb
Binary files /dev/null and b/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn.zip 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 (file)
index 0000000..d3c3ee5
Binary files /dev/null and b/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn-with-merge.zip 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 (file)
index 0000000..291d4fb
Binary files /dev/null and b/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn.zip 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 (file)
index 0000000..852a05e
Binary files /dev/null and b/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn-with-merge.zip 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 (file)
index 0000000..f31352c
Binary files /dev/null and b/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn.zip 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 (file)
index 0000000..ca672fd
Binary files /dev/null and b/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn-with-merge.zip 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 (file)
index 0000000..39b241c
Binary files /dev/null and b/sonar-scanner-engine/src/it/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn.zip differ
index fe86282effef20fe76b7d252a1ce5d9700775002..5cce16465ac476619ece730b3f2a2c243f20974c 100644 (file)
@@ -56,7 +56,7 @@ public class WriteCacheImplTest {
   }
 
   @Test
-  public void write_bytes_adds_entries() throws IOException {
+  public void write_bytes_adds_entries()  {
     byte[] b1 = new byte[] {1, 2, 3};
     byte[] b2 = new byte[] {3, 4};
     writeCache.write("key", b1);
@@ -66,7 +66,7 @@ public class WriteCacheImplTest {
   }
 
   @Test
-  public void dont_write_if_its_pull_request() throws IOException {
+  public void dont_write_if_its_pull_request()  {
     byte[] b1 = new byte[] {1, 2, 3};
     when(branchConfiguration.isPullRequest()).thenReturn(true);
     writeCache.write("key1", b1);
@@ -75,7 +75,7 @@ public class WriteCacheImplTest {
   }
 
   @Test
-  public void write_inputStream_adds_entries() throws IOException {
+  public void write_inputStream_adds_entries()  {
     byte[] b1 = new byte[] {1, 2, 3};
     byte[] b2 = new byte[] {3, 4};
     writeCache.write("key", new ByteArrayInputStream(b1));
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/LogOutputRecorder.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/LogOutputRecorder.java
deleted file mode 100644 (file)
index 41ad59d..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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 com.google.common.collect.LinkedHashMultimap;
-import com.google.common.collect.Multimap;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.sonar.batch.bootstrapper.LogOutput;
-
-public class LogOutputRecorder implements LogOutput {
-  private final Multimap<String, String> recordedByLevel = LinkedHashMultimap.create();
-  private final List<String> recorded = new LinkedList<>();
-  private final StringBuffer asString = new StringBuffer();
-
-  @Override
-  public synchronized void log(String formattedMessage, Level level) {
-    recordedByLevel.put(level.toString(), formattedMessage);
-    recorded.add(formattedMessage);
-    asString.append(formattedMessage).append("\n");
-  }
-
-  public synchronized Collection<String> getAll() {
-    return new ArrayList<>(recorded);
-  }
-
-  public synchronized String getAllAsString() {
-    return String.join("\n", recorded);
-  }
-
-  public synchronized Collection<String> get(String level) {
-    return new ArrayList<>(recordedByLevel.get(level));
-  }
-
-  public synchronized String getAsString() {
-    return asString.toString();
-  }
-
-}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java
deleted file mode 100644 (file)
index 26d02e9..0000000
+++ /dev/null
@@ -1,582 +0,0 @@
-/*
- * 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<org.sonarqube.ws.Rules.ListResponse.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/test/java/org/sonar/scanner/mediumtest/branch/BranchMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/branch/BranchMediumTest.java
deleted file mode 100644 (file)
index 1ec51e0..0000000
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * 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 BranchMediumTest {
-
-  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() throws Exception {
-    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/test/java/org/sonar/scanner/mediumtest/branch/DeprecatedBranchMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/branch/DeprecatedBranchMediumTest.java
deleted file mode 100644 (file)
index 8dff5b1..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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 DeprecatedBranchMediumTest {
-
-  @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/test/java/org/sonar/scanner/mediumtest/coverage/CoverageMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/coverage/CoverageMediumTest.java
deleted file mode 100644 (file)
index 145ec99..0000000
+++ /dev/null
@@ -1,368 +0,0 @@
-/*
- * 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 CoverageMediumTest {
-
-  @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/test/java/org/sonar/scanner/mediumtest/coverage/GenericCoverageMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/coverage/GenericCoverageMediumTest.java
deleted file mode 100644 (file)
index 8a49145..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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 GenericCoverageMediumTest {
-  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/test/java/org/sonar/scanner/mediumtest/cpd/CpdMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/cpd/CpdMediumTest.java
deleted file mode 100644 (file)
index b507a80..0000000
+++ /dev/null
@@ -1,473 +0,0 @@
-/*
- * 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 CpdMediumTest {
-  @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/test/java/org/sonar/scanner/mediumtest/fs/FileSystemMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/fs/FileSystemMediumTest.java
deleted file mode 100644 (file)
index 4aa35bc..0000000
+++ /dev/null
@@ -1,1282 +0,0 @@
-/*
- * 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 FileSystemMediumTest {
-
-  @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/test/java/org/sonar/scanner/mediumtest/fs/NoLanguagesPluginsMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/fs/NoLanguagesPluginsMediumTest.java
deleted file mode 100644 (file)
index f69b1f5..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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 NoLanguagesPluginsMediumTest {
-  @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/test/java/org/sonar/scanner/mediumtest/fs/ProjectBuilderMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/fs/ProjectBuilderMediumTest.java
deleted file mode 100644 (file)
index cc0fd5d..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * 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 ProjectBuilderMediumTest {
-
-  @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/test/java/org/sonar/scanner/mediumtest/highlighting/HighlightingMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/highlighting/HighlightingMediumTest.java
deleted file mode 100644 (file)
index 0d04f72..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * 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 HighlightingMediumTest {
-
-  @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/test/java/org/sonar/scanner/mediumtest/issues/ChecksMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/ChecksMediumTest.java
deleted file mode 100644 (file)
index 35f9b60..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * 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 ChecksMediumTest {
-
-  @org.junit.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/test/java/org/sonar/scanner/mediumtest/issues/ExternalIssuesMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/ExternalIssuesMediumTest.java
deleted file mode 100644 (file)
index 88e9c3f..0000000
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * 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.ScannerMediumTester;
-import org.sonar.scanner.mediumtest.AnalysisResult;
-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 ExternalIssuesMediumTest {
-  @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/test/java/org/sonar/scanner/mediumtest/issues/IssuesMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/IssuesMediumTest.java
deleted file mode 100644 (file)
index adf860c..0000000
+++ /dev/null
@@ -1,452 +0,0 @@
-/*
- * 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 IssuesMediumTest {
-
-  @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/test/java/org/sonar/scanner/mediumtest/issues/IssuesOnDirMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/IssuesOnDirMediumTest.java
deleted file mode 100644 (file)
index 9f84a38..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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.ScannerMediumTester;
-import org.sonar.scanner.mediumtest.AnalysisResult;
-import org.sonar.xoo.XooPlugin;
-import org.sonar.xoo.rule.XooRulesDefinition;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class IssuesOnDirMediumTest {
-
-  @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/test/java/org/sonar/scanner/mediumtest/issues/IssuesOnModuleMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/IssuesOnModuleMediumTest.java
deleted file mode 100644 (file)
index 4e18a60..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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.ScannerMediumTester;
-import org.sonar.scanner.mediumtest.AnalysisResult;
-import org.sonar.xoo.XooPlugin;
-import org.sonar.xoo.rule.XooRulesDefinition;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class IssuesOnModuleMediumTest {
-
-  @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/test/java/org/sonar/scanner/mediumtest/issues/MultilineIssuesMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/MultilineIssuesMediumTest.java
deleted file mode 100644 (file)
index 3e2d06f..0000000
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * 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 MultilineIssuesMediumTest {
-
-  @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/test/java/org/sonar/scanner/mediumtest/issues/PreviewMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/PreviewMediumTest.java
deleted file mode 100644 (file)
index 630233a..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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 PreviewMediumTest {
-
-  @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/test/java/org/sonar/scanner/mediumtest/log/ExceptionHandlingMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/log/ExceptionHandlingMediumTest.java
deleted file mode 100644 (file)
index 011c7b5..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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 ExceptionHandlingMediumTest {
-
-  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/test/java/org/sonar/scanner/mediumtest/log/LogListenerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/log/LogListenerTest.java
deleted file mode 100644 (file)
index e8f7099..0000000
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * 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 LogListenerTest {
-  @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 (LogOutput.Level l : LogOutput.Level.values()) {
-      assertThat(msg).doesNotContain(l.toString());
-    }
-
-    Matcher matcher = simpleTimePattern.matcher(msg);
-    assertThat(matcher.find()).isFalse();
-  }
-
-  @Test
-  public void testChangeLogForAnalysis() throws IOException {
-    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;
-    LogOutput.Level level;
-
-    LogEvent(String msg, LogOutput.Level level) {
-      this.msg = msg;
-      this.level = level;
-    }
-  }
-}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/measures/MeasuresMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/measures/MeasuresMediumTest.java
deleted file mode 100644 (file)
index ff9c684..0000000
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * 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.ScannerMediumTester;
-import org.sonar.scanner.mediumtest.AnalysisResult;
-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 MeasuresMediumTest {
-
-  @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/test/java/org/sonar/scanner/mediumtest/scm/ScmMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/scm/ScmMediumTest.java
deleted file mode 100644 (file)
index ec81163..0000000
+++ /dev/null
@@ -1,358 +0,0 @@
-/*
- * 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 ScmMediumTest {
-
-  private static final String MISSING_BLAME_INFORMATION_FOR_THE_FOLLOWING_FILES = "Missing blame information for the following files:";
-  private static final String CHANGED_CONTENT_SCM_ON_SERVER_XOO = "src/changed_content_scm_on_server.xoo";
-  private static final String 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/test/java/org/sonar/scanner/mediumtest/symbol/SymbolMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/symbol/SymbolMediumTest.java
deleted file mode 100644 (file)
index dad6899..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * 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.ScannerMediumTester;
-import org.sonar.scanner.mediumtest.AnalysisResult;
-import org.sonar.scanner.protocol.output.ScannerReport;
-import org.sonar.xoo.XooPlugin;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class SymbolMediumTest {
-
-  @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/test/java/org/sonar/scanner/mediumtest/tasks/TasksMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/tasks/TasksMediumTest.java
deleted file mode 100644 (file)
index 436507b..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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 TasksMediumTest {
-
-  @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/test/java/org/sonar/scanner/mediumtest/tests/GenericTestExecutionMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/tests/GenericTestExecutionMediumTest.java
deleted file mode 100644 (file)
index d57b519..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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 GenericTestExecutionMediumTest {
-  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'"));
-  }
-
-}
index c977c7eaa9fe0da973f7c9c8622cec5af38a7d67..46b33f5d076476982428c6ab680f211b993ae632 100644 (file)
@@ -25,10 +25,10 @@ import static org.assertj.core.api.Assertions.assertThat;
 
 public class JavaArchitectureInformationProviderTest {
 
-  private JavaArchitectureInformationProvider javaArchitectureInformationProvider = new JavaArchitectureInformationProvider();
+  private final JavaArchitectureInformationProvider javaArchitectureInformationProvider = new JavaArchitectureInformationProvider();
 
   @Test
-  public void is64bitJavaVersion_returnsTrue_whenRunningWith64bitJava() {
+  public void is64bitJavaVersion_whenRunningWith64bitJava_shouldReturnTrue() {
     assertThat(javaArchitectureInformationProvider.is64bitJavaVersion()).isTrue();
   }
 
index eab74489508e994413b6115b5dd41e1cfcd28f6c..48afa8d0108d1a503d2de653552c01cd4672a1fa 100644 (file)
@@ -27,7 +27,6 @@ import org.junit.Test;
 import org.sonar.api.utils.MessageException;
 import org.sonar.scanner.WsTestUtil;
 import org.sonar.scanner.bootstrap.DefaultScannerWsClient;
-import org.sonar.scanner.scan.ScanProperties;
 import org.sonarqube.ws.Qualityprofiles;
 import org.sonarqube.ws.Qualityprofiles.SearchWsResponse.QualityProfile;
 import org.sonarqube.ws.client.HttpException;
@@ -38,7 +37,6 @@ import static org.mockito.Mockito.mock;
 public class DefaultQualityProfileLoaderTest {
 
   private final DefaultScannerWsClient wsClient = mock(DefaultScannerWsClient.class);
-  private final ScanProperties properties = mock(ScanProperties.class);
   private final DefaultQualityProfileLoader underTest = new DefaultQualityProfileLoader(wsClient);
 
   @Test
index 82485945112b0614f8f37bf3c870006fecdcd489..2ea26c6cc6846703b67768e5b9478e84f4bb507e 100644 (file)
@@ -19,7 +19,6 @@
  */
 package org.sonar.scanner.repository;
 
-import java.util.Date;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -33,7 +32,6 @@ public class SingleProjectRepositoryTest {
 
   @Before
   public void setUp() {
-    Date lastAnalysisDate = new Date();
     repository = new SingleProjectRepository(singletonMap("/Abc.java", new FileData("123", "456")));
   }
 
index 30692f55434e4af098bc6457da114bb92d9fc4f9..03f1547bab2d123f1817269178c1920a91f13042 100644 (file)
@@ -41,7 +41,6 @@ public class LanguageDetectionTest {
   @Rule
   public TemporaryFolder temp = new TemporaryFolder();
 
-
   private MapSettings settings;
 
   @Before
index 86dc4e81b177e75c7929db0ff0874581121a26d9..30db816f4f3ef8a28095bfe983352f3a4a14163b 100644 (file)
@@ -60,39 +60,40 @@ public class ModuleSensorContextTest {
   private final UnchangedFilesHandler unchangedFilesHandler = mock(UnchangedFilesHandler.class);
   private final SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.parse("5.5"), SonarQubeSide.SCANNER, SonarEdition.COMMUNITY);
   private DefaultFileSystem fs;
-  private ModuleSensorContext adaptor;
+  private ModuleSensorContext underTest;
 
   @Before
   public void prepare() throws Exception {
     fs = new DefaultFileSystem(temp.newFolder().toPath());
-    adaptor = new ModuleSensorContext(mock(DefaultInputProject.class), mock(InputModule.class), settings.asConfig(), settings, fs, activeRules, sensorStorage, runtime,
+    underTest = new ModuleSensorContext(mock(DefaultInputProject.class), mock(InputModule.class), settings.asConfig(), settings, fs, activeRules, sensorStorage, runtime,
       branchConfiguration, writeCache, readCache, analysisCacheEnabled, unchangedFilesHandler);
   }
 
   @Test
-  public void shouldProvideComponents() {
-    assertThat(adaptor.activeRules()).isEqualTo(activeRules);
-    assertThat(adaptor.fileSystem()).isEqualTo(fs);
-    assertThat(adaptor.getSonarQubeVersion()).isEqualTo(Version.parse("5.5"));
-    assertThat(adaptor.runtime()).isEqualTo(runtime);
-    assertThat(adaptor.canSkipUnchangedFiles()).isFalse();
+  public void shouldProvideComponents_returnsNotNull() {
+    assertThat(underTest.activeRules()).isEqualTo(activeRules);
+    assertThat(underTest.fileSystem()).isEqualTo(fs);
+    assertThat(underTest.getSonarQubeVersion()).isEqualTo(Version.parse("5.5"));
+    assertThat(underTest.runtime()).isEqualTo(runtime);
+    assertThat(underTest.canSkipUnchangedFiles()).isFalse();
 
-    assertThat(adaptor.nextCache()).isEqualTo(writeCache);
-    assertThat(adaptor.previousCache()).isEqualTo(readCache);
+    assertThat(underTest.nextCache()).isEqualTo(writeCache);
+    assertThat(underTest.previousCache()).isEqualTo(readCache);
 
-    assertThat(adaptor.newIssue()).isNotNull();
-    assertThat(adaptor.newExternalIssue()).isNotNull();
-    assertThat(adaptor.newAdHocRule()).isNotNull();
-    assertThat(adaptor.newMeasure()).isNotNull();
-    assertThat(adaptor.newAnalysisError()).isEqualTo(ModuleSensorContext.NO_OP_NEW_ANALYSIS_ERROR);
-    assertThat(adaptor.isCancelled()).isFalse();
-    assertThat(adaptor.newSignificantCode()).isNotNull();
+    assertThat(underTest.newIssue()).isNotNull();
+    assertThat(underTest.newExternalIssue()).isNotNull();
+    assertThat(underTest.newAdHocRule()).isNotNull();
+    assertThat(underTest.newMeasure()).isNotNull();
+    assertThat(underTest.newAnalysisError()).isEqualTo(ModuleSensorContext.NO_OP_NEW_ANALYSIS_ERROR);
+    assertThat(underTest.isCancelled()).isFalse();
+    assertThat(underTest.newSignificantCode()).isNotNull();
   }
 
   @Test
   public void should_delegate_to_unchanged_files_handler() {
     DefaultInputFile defaultInputFile = mock(DefaultInputFile.class);
-    adaptor.markAsUnchanged(defaultInputFile);
+
+    underTest.markAsUnchanged(defaultInputFile);
 
     verify(unchangedFilesHandler).markAsUnchanged(defaultInputFile);
   }
@@ -100,9 +101,9 @@ public class ModuleSensorContextTest {
   @Test
   public void pull_request_can_skip_unchanged_files() {
     when(branchConfiguration.isPullRequest()).thenReturn(true);
-    adaptor = new ModuleSensorContext(mock(DefaultInputProject.class), mock(InputModule.class), settings.asConfig(), settings, fs, activeRules, sensorStorage, runtime,
+    underTest = new ModuleSensorContext(mock(DefaultInputProject.class), mock(InputModule.class), settings.asConfig(), settings, fs, activeRules, sensorStorage, runtime,
       branchConfiguration, writeCache, readCache, analysisCacheEnabled, unchangedFilesHandler);
-    assertThat(adaptor.canSkipUnchangedFiles()).isTrue();
+    assertThat(underTest.canSkipUnchangedFiles()).isTrue();
   }
 
 }
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnBlameCommandTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnBlameCommandTest.java
deleted file mode 100644 (file)
index 3683598..0000000
+++ /dev/null
@@ -1,391 +0,0 @@
-/*
- * 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 SvnBlameCommandTest {
-
-  /*
-   * 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 SvnBlameCommandTest(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));
-  }
-}
index 38a51496f9786dccdd9c46cfe221369b4d153a95..101c01f54277fa2f4a86bf86306709e57a2d40ca 100644 (file)
@@ -30,21 +30,23 @@ public class SvnScmSupportTest {
   private SvnConfiguration config = mock(SvnConfiguration.class);
 
   @Test
-  public void getExtensions() {
+  public void getObjects_shouldNotBeEmpty() {
     assertThat(SvnScmSupport.getObjects()).isNotEmpty();
   }
 
   @Test
-  public void newSvnClientManager_with_auth() {
+  public void newSvnClientManager_whenPasswordConfigured_shouldNotReturnNull() {
     when(config.password()).thenReturn("password");
     when(config.passPhrase()).thenReturn("passPhrase");
+
     assertThat(newSvnClientManager(config)).isNotNull();
   }
 
   @Test
-  public void newSvnClientManager_without_auth() {
+  public void newSvnClientManager_whenPasswordNotConfigured_shouldNotReturnNull() {
     assertThat(config.password()).isNull();
     assertThat(config.passPhrase()).isNull();
+
     assertThat(newSvnClientManager(config)).isNotNull();
   }
 
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnTester.java b/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnTester.java
deleted file mode 100644 (file)
index 5b45ab2..0000000
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * 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 createBranch(String branchSource, String branchName) throws IOException, SVNException {
-    SVNCopyClient copyClient = manager.getCopyClient();
-    SVNCopySource source = new SVNCopySource(SVNRevision.HEAD, SVNRevision.HEAD, localRepository.appendPath(branchSource, 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/test/java/org/sonar/scm/svn/SvnTesterTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scm/svn/SvnTesterTest.java
deleted file mode 100644 (file)
index e5c7ad7..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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 SvnTesterTest {
-  @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/test/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn-with-merge.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn-with-merge.zip
deleted file mode 100644 (file)
index d3c3ee5..0000000
Binary files a/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn-with-merge.zip and /dev/null differ
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn.zip
deleted file mode 100644 (file)
index 291d4fb..0000000
Binary files a/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.6/repo-svn.zip and /dev/null differ
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn-with-merge.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn-with-merge.zip
deleted file mode 100644 (file)
index d3c3ee5..0000000
Binary files a/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn-with-merge.zip and /dev/null differ
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn.zip
deleted file mode 100644 (file)
index 291d4fb..0000000
Binary files a/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.7/repo-svn.zip and /dev/null differ
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn-with-merge.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn-with-merge.zip
deleted file mode 100644 (file)
index 852a05e..0000000
Binary files a/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn-with-merge.zip and /dev/null differ
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn.zip
deleted file mode 100644 (file)
index f31352c..0000000
Binary files a/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.8/repo-svn.zip and /dev/null differ
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn-with-merge.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn-with-merge.zip
deleted file mode 100644 (file)
index ca672fd..0000000
Binary files a/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn-with-merge.zip and /dev/null differ
diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn.zip b/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn.zip
deleted file mode 100644 (file)
index 39b241c..0000000
Binary files a/sonar-scanner-engine/src/test/resources/org/sonar/scm/svn/test-repos/1.9/repo-svn.zip and /dev/null differ