]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-22914 Move the ScannerMediumTester to testFixtures
authorJulien HENRY <julien.henry@sonarsource.com>
Wed, 4 Sep 2024 09:46:59 +0000 (11:46 +0200)
committersonartech <sonartech@sonarsource.com>
Thu, 12 Sep 2024 20:02:54 +0000 (20:02 +0000)
12 files changed:
sonar-scanner-engine/build.gradle
sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java [deleted file]
sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/AnalysisResult.java [deleted file]
sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/FakeLanguagesLoader.java [deleted file]
sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/FakeLanguagesProvider.java [deleted file]
sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/FakePluginInstaller.java [deleted file]
sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/AnalysisResult.java [new file with mode: 0644]
sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/FakeLanguagesLoader.java [new file with mode: 0644]
sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/FakeLanguagesProvider.java [new file with mode: 0644]
sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/FakePluginInstaller.java [new file with mode: 0644]
sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java [new file with mode: 0644]
sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/package-info.java [new file with mode: 0644]

index 121d7eec29557e913d5bddc44260d9f1020430eb..4a7d8bc12ea3a1cefcefa4715b4002089636086b 100644 (file)
@@ -1,3 +1,7 @@
+plugins {
+  id 'java-test-fixtures'
+}
+
 sonar {
   properties {
     property 'sonar.projectName', "${projectTitle} :: Scanner Engine"
@@ -50,6 +54,8 @@ dependencies {
 
   compileOnlyApi 'com.github.spotbugs:spotbugs-annotations'
 
+  testFixturesImplementation 'org.junit.jupiter:junit-jupiter-api'
+
   testImplementation 'com.squareup.okhttp3:mockwebserver'
   testImplementation 'com.squareup.okhttp3:okhttp'
   testImplementation 'com.squareup.okio:okio'
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
deleted file mode 100644 (file)
index 59eb629..0000000
+++ /dev/null
@@ -1,616 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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 java.util.Set;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import javax.annotation.Priority;
-import org.apache.commons.io.FileUtils;
-import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
-import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
-import org.junit.jupiter.api.extension.ExtensionContext;
-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.Rule;
-
-import static java.util.Collections.emptySet;
-
-/**
- * Main utility class for writing scanner medium tests.
- */
-public class ScannerMediumTester extends ExternalResource implements BeforeTestExecutionCallback, AfterTestExecutionCallback {
-
-  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 final FakeLanguagesLoader languagesLoader = new FakeLanguagesLoader();
-  private final FakeLanguagesProvider languagesProvider = new FakeLanguagesProvider();
-  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) {
-    pluginInstaller.add(pluginKey, instance);
-    return this;
-  }
-
-  public ScannerMediumTester registerOptionalPlugin(String pluginKey, Set<String> requiredForLanguages, Plugin instance) {
-    pluginInstaller.addOptional(pluginKey, requiredForLanguages, instance);
-    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.setRepo(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
-  public void afterTestExecution(ExtensionContext extensionContext) {
-    after();
-  }
-
-  @Override
-  public void beforeTestExecution(ExtensionContext extensionContext) {
-    before();
-  }
-
-  @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 void addLanguage(String key, String name, String... suffixes) {
-    languagesLoader.addLanguage(key, name, suffixes, new String[0]);
-    languagesProvider.addLanguage(key, name, true);
-  }
-
-  public void addLanguage(String key, String name, boolean publishAllFiles, String... suffixes) {
-    languagesLoader.addLanguage(key, name, suffixes, new String[0]);
-    languagesProvider.addLanguage(key, name, publishAllFiles);
-  }
-
-  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 builder = 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,
-          tester.languagesLoader,
-          tester.languagesProvider,
-          result);
-      if (tester.logOutput != null) {
-        builder.setLogOutput(tester.logOutput);
-      } else {
-        builder.setEnableLoggingConfiguration(false);
-      }
-      builder.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/main/java/org/sonar/scanner/mediumtest/AnalysisResult.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/AnalysisResult.java
deleted file mode 100644 (file)
index c4c8d09..0000000
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import javax.annotation.CheckForNull;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.batch.fs.InputComponent;
-import org.sonar.api.batch.fs.InputFile;
-import org.sonar.api.batch.fs.TextPointer;
-import org.sonar.api.batch.fs.TextRange;
-import org.sonar.api.batch.fs.internal.DefaultInputComponent;
-import org.sonar.api.batch.fs.internal.DefaultInputFile;
-import org.sonar.api.batch.sensor.highlighting.TypeOfText;
-import org.sonar.api.scanner.fs.InputProject;
-import org.sonar.core.util.CloseableIterator;
-import org.sonar.scanner.protocol.output.FileStructure;
-import org.sonar.scanner.protocol.output.ScannerReport;
-import org.sonar.scanner.protocol.output.ScannerReport.Component;
-import org.sonar.scanner.protocol.output.ScannerReport.Symbol;
-import org.sonar.scanner.protocol.output.ScannerReportReader;
-import org.sonar.scanner.report.ScannerReportUtils;
-import org.sonar.scanner.scan.SpringProjectScanContainer;
-import org.sonar.scanner.scan.filesystem.InputComponentStore;
-
-public class AnalysisResult implements AnalysisObserver {
-
-  private static final Logger LOG = LoggerFactory.getLogger(AnalysisResult.class);
-
-  private Map<String, InputFile> inputFilesByKeys = new HashMap<>();
-  private InputProject project;
-  private ScannerReportReader reader;
-
-  @Override
-  public void analysisCompleted(SpringProjectScanContainer container) {
-    LOG.info("Store analysis results in memory for later assertions in medium test");
-    FileStructure fileStructure = container.getComponentByType(FileStructure.class);
-    reader = new ScannerReportReader(fileStructure);
-    project = container.getComponentByType(InputProject.class);
-
-    storeFs(container);
-
-  }
-
-  public ScannerReportReader getReportReader() {
-    return reader;
-  }
-
-  private void storeFs(SpringProjectScanContainer container) {
-    InputComponentStore inputFileCache = container.getComponentByType(InputComponentStore.class);
-    for (InputFile inputPath : inputFileCache.inputFiles()) {
-      inputFilesByKeys.put(((DefaultInputFile) inputPath).getProjectRelativePath(), inputPath);
-    }
-  }
-
-  public Component getReportComponent(InputComponent inputComponent) {
-    return getReportReader().readComponent(((DefaultInputComponent) inputComponent).scannerId());
-  }
-
-  public Component getReportComponent(int scannerId) {
-    return getReportReader().readComponent(scannerId);
-  }
-
-  public List<ScannerReport.Issue> issuesFor(InputComponent inputComponent) {
-    return issuesFor(((DefaultInputComponent) inputComponent).scannerId());
-  }
-
-  public List<ScannerReport.ExternalIssue> externalIssuesFor(InputComponent inputComponent) {
-    return externalIssuesFor(((DefaultInputComponent) inputComponent).scannerId());
-  }
-
-  public List<ScannerReport.Issue> issuesFor(Component reportComponent) {
-    int ref = reportComponent.getRef();
-    return issuesFor(ref);
-  }
-
-  private List<ScannerReport.Issue> issuesFor(int ref) {
-    List<ScannerReport.Issue> result = new ArrayList<>();
-    try (CloseableIterator<ScannerReport.Issue> it = reader.readComponentIssues(ref)) {
-      while (it.hasNext()) {
-        result.add(it.next());
-      }
-    }
-    return result;
-  }
-
-  private List<ScannerReport.ExternalIssue> externalIssuesFor(int ref) {
-    List<ScannerReport.ExternalIssue> result = new ArrayList<>();
-    try (CloseableIterator<ScannerReport.ExternalIssue> it = reader.readComponentExternalIssues(ref)) {
-      while (it.hasNext()) {
-        result.add(it.next());
-      }
-    }
-    return result;
-  }
-
-  public InputProject project() {
-    return project;
-  }
-
-  public Collection<InputFile> inputFiles() {
-    return inputFilesByKeys.values();
-  }
-
-  @CheckForNull
-  public InputFile inputFile(String relativePath) {
-    return inputFilesByKeys.get(relativePath);
-  }
-
-  public Map<String, List<ScannerReport.Measure>> allMeasures() {
-    Map<String, List<ScannerReport.Measure>> result = new HashMap<>();
-    List<ScannerReport.Measure> projectMeasures = new ArrayList<>();
-    try (CloseableIterator<ScannerReport.Measure> it = reader.readComponentMeasures(((DefaultInputComponent) project).scannerId())) {
-      while (it.hasNext()) {
-        projectMeasures.add(it.next());
-      }
-    }
-    result.put(project.key(), projectMeasures);
-    for (InputFile inputFile : inputFilesByKeys.values()) {
-      List<ScannerReport.Measure> measures = new ArrayList<>();
-      try (CloseableIterator<ScannerReport.Measure> it = reader.readComponentMeasures(((DefaultInputComponent) inputFile).scannerId())) {
-        while (it.hasNext()) {
-          measures.add(it.next());
-        }
-      }
-      result.put(inputFile.key(), measures);
-    }
-    return result;
-  }
-
-  /**
-   * Get highlighting types at a given position in an inputfile
-   *
-   * @param lineOffset 0-based offset in file
-   */
-  public List<TypeOfText> highlightingTypeFor(InputFile file, int line, int lineOffset) {
-    int ref = ((DefaultInputComponent) file).scannerId();
-    if (!reader.hasSyntaxHighlighting(ref)) {
-      return Collections.emptyList();
-    }
-    TextPointer pointer = file.newPointer(line, lineOffset);
-    List<TypeOfText> result = new ArrayList<>();
-    try (CloseableIterator<ScannerReport.SyntaxHighlightingRule> it = reader.readComponentSyntaxHighlighting(ref)) {
-      while (it.hasNext()) {
-        ScannerReport.SyntaxHighlightingRule rule = it.next();
-        TextRange ruleRange = toRange(file, rule.getRange());
-        if (ruleRange.start().compareTo(pointer) <= 0 && ruleRange.end().compareTo(pointer) > 0) {
-          result.add(ScannerReportUtils.toBatchType(rule.getType()));
-        }
-      }
-    } catch (Exception e) {
-      throw new IllegalStateException("Can't read syntax highlighting for " + file, e);
-    }
-    return result;
-  }
-
-  private static TextRange toRange(InputFile file, ScannerReport.TextRange reportRange) {
-    return file.newRange(file.newPointer(reportRange.getStartLine(), reportRange.getStartOffset()), file.newPointer(reportRange.getEndLine(), reportRange.getEndOffset()));
-  }
-
-  /**
-   * Get list of all start positions of a symbol in an inputfile
-   *
-   * @param symbolStartLine       0-based start offset for the symbol in file
-   * @param symbolStartLineOffset 0-based end offset for the symbol in file
-   */
-  @CheckForNull
-  public List<ScannerReport.TextRange> symbolReferencesFor(InputFile file, int symbolStartLine, int symbolStartLineOffset) {
-    int ref = ((DefaultInputComponent) file).scannerId();
-    try (CloseableIterator<Symbol> symbols = getReportReader().readComponentSymbols(ref)) {
-      while (symbols.hasNext()) {
-        Symbol symbol = symbols.next();
-        if (symbol.getDeclaration().getStartLine() == symbolStartLine && symbol.getDeclaration().getStartOffset() == symbolStartLineOffset) {
-          return symbol.getReferenceList();
-        }
-      }
-    }
-    return Collections.emptyList();
-  }
-
-  public List<ScannerReport.Duplication> duplicationsFor(InputFile file) {
-    List<ScannerReport.Duplication> result = new ArrayList<>();
-    int ref = ((DefaultInputComponent) file).scannerId();
-    try (CloseableIterator<ScannerReport.Duplication> it = getReportReader().readComponentDuplications(ref)) {
-      while (it.hasNext()) {
-        result.add(it.next());
-      }
-    } catch (Exception e) {
-      throw new IllegalStateException(e);
-    }
-    return result;
-  }
-
-  public List<ScannerReport.CpdTextBlock> duplicationBlocksFor(InputFile file) {
-    List<ScannerReport.CpdTextBlock> result = new ArrayList<>();
-    int ref = ((DefaultInputComponent) file).scannerId();
-    try (CloseableIterator<ScannerReport.CpdTextBlock> it = getReportReader().readCpdTextBlocks(ref)) {
-      while (it.hasNext()) {
-        result.add(it.next());
-      }
-    } catch (Exception e) {
-      throw new IllegalStateException(e);
-    }
-    return result;
-  }
-
-  @CheckForNull
-  public ScannerReport.LineCoverage coverageFor(InputFile file, int line) {
-    int ref = ((DefaultInputComponent) file).scannerId();
-    try (CloseableIterator<ScannerReport.LineCoverage> it = getReportReader().readComponentCoverage(ref)) {
-      while (it.hasNext()) {
-        ScannerReport.LineCoverage coverage = it.next();
-        if (coverage.getLine() == line) {
-          return coverage;
-        }
-      }
-    } catch (Exception e) {
-      throw new IllegalStateException(e);
-    }
-    return null;
-  }
-
-  public List<ScannerReport.AdHocRule> adHocRules() {
-    List<ScannerReport.AdHocRule> result = new ArrayList<>();
-    try (CloseableIterator<ScannerReport.AdHocRule> it = getReportReader().readAdHocRules()) {
-      while (it.hasNext()) {
-        result.add(it.next());
-      }
-    } catch (Exception e) {
-      throw new IllegalStateException(e);
-    }
-    return result;
-  }
-}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/FakeLanguagesLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/FakeLanguagesLoader.java
deleted file mode 100644 (file)
index f2e2f48..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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.util.HashMap;
-import java.util.Map;
-import javax.annotation.Priority;
-import org.sonar.api.resources.Languages;
-import org.sonar.scanner.repository.language.Language;
-import org.sonar.scanner.repository.language.LanguagesLoader;
-import org.sonar.scanner.repository.language.SupportedLanguageDto;
-
-@Priority(1)
-public class FakeLanguagesLoader implements LanguagesLoader {
-
-  private final Map<String, Language> languageMap = new HashMap<>();
-
-  public FakeLanguagesLoader() {
-    languageMap.put("xoo", new Language(new SupportedLanguageDto("xoo", "xoo", new String[] { ".xoo" }, new String[0])));
-  }
-
-  public FakeLanguagesLoader(Languages languages) {
-    for (org.sonar.api.resources.Language language : languages.all()) {
-      languageMap.put(language.getKey(), new Language(new SupportedLanguageDto(language.getKey(), language.getName(), language.getFileSuffixes(), language.filenamePatterns())));
-    }
-  }
-  @Override
-  public Map<String, Language> load() {
-    return languageMap;
-  }
-
-  public void addLanguage(String key, String name, String[] suffixes, String[] filenamePatterns) {
-    languageMap.put(key, new Language(new SupportedLanguageDto(key, name, suffixes, filenamePatterns)));
-  }
-
-}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/FakeLanguagesProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/FakeLanguagesProvider.java
deleted file mode 100644 (file)
index 6009746..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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 javax.annotation.Priority;
-import org.sonar.api.resources.Language;
-import org.sonar.api.resources.Languages;
-import org.springframework.context.annotation.Bean;
-
-@Priority(1)
-public class FakeLanguagesProvider {
-
-  private Languages languages = new Languages();
-
-  @Bean("Languages")
-  public Languages provide() {
-    return this.languages;
-  }
-
-  public void addLanguage(String key, String name, boolean publishAllFiles) {
-    this.languages.add(new FakeLanguage(key, name, publishAllFiles));
-  }
-
-  private static class FakeLanguage implements Language {
-
-    private final String name;
-    private final String key;
-    private final boolean publishAllFiles;
-
-    public FakeLanguage(String key, String name, boolean publishAllFiles) {
-      this.name = name;
-      this.key = key;
-      this.publishAllFiles = publishAllFiles;
-    }
-
-    @Override
-    public String getKey() {
-      return this.key;
-    }
-
-    @Override
-    public String getName() {
-      return this.name;
-    }
-
-    @Override
-    public String[] getFileSuffixes() {
-      return new String[0];
-    }
-
-    @Override
-    public boolean publishAllFiles() {
-      return this.publishAllFiles;
-    }
-  }
-
-
-}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/FakePluginInstaller.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/FakePluginInstaller.java
deleted file mode 100644 (file)
index a735827..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import javax.annotation.Priority;
-import org.sonar.api.Plugin;
-import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.plugin.PluginType;
-import org.sonar.scanner.bootstrap.PluginInstaller;
-import org.sonar.scanner.bootstrap.ScannerPlugin;
-
-@Priority(1)
-public class FakePluginInstaller implements PluginInstaller {
-
-  private final Map<String, ScannerPlugin> pluginsByKeys = new HashMap<>();
-  private final List<LocalPlugin> mediumTestPlugins = new ArrayList<>();
-  private final List<LocalPlugin> optionalMediumTestPlugins = new ArrayList<>();
-
-  public FakePluginInstaller add(String pluginKey, File jarFile, long lastUpdatedAt) {
-    pluginsByKeys.put(pluginKey, new ScannerPlugin(pluginKey, lastUpdatedAt, PluginType.BUNDLED, PluginInfo.create(jarFile)));
-    return this;
-  }
-
-  public FakePluginInstaller add(String pluginKey, Plugin instance) {
-    mediumTestPlugins.add(new LocalPlugin(pluginKey, instance, Set.of()));
-    return this;
-  }
-
-  public FakePluginInstaller addOptional(String pluginKey, Set<String> requiredForLanguages, Plugin instance) {
-    optionalMediumTestPlugins.add(new LocalPlugin(pluginKey, instance, requiredForLanguages));
-    return this;
-  }
-
-  @Override
-  public Map<String, ScannerPlugin> installAllPlugins() {
-    return pluginsByKeys;
-  }
-
-  @Override
-  public Map<String, ScannerPlugin> installRequiredPlugins() {
-    return pluginsByKeys;
-  }
-
-  @Override
-  public Map<String, ScannerPlugin> installPluginsForLanguages(Set<String> languageKeys) {
-    return pluginsByKeys;
-  }
-
-  @Override
-  public List<LocalPlugin> installLocals() {
-    return mediumTestPlugins;
-  }
-
-  @Override
-  public List<LocalPlugin> installOptionalLocals(Set<String> languageKeys) {
-    return optionalMediumTestPlugins.stream()
-      .filter(plugin -> languageKeys.stream().anyMatch(lang -> plugin.requiredForLanguages().contains(lang)))
-      .toList();
-  }
-}
diff --git a/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/AnalysisResult.java b/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/AnalysisResult.java
new file mode 100644 (file)
index 0000000..e3f2829
--- /dev/null
@@ -0,0 +1,226 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import javax.annotation.CheckForNull;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.fs.InputComponent;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.TextPointer;
+import org.sonar.api.batch.fs.TextRange;
+import org.sonar.api.batch.fs.internal.DefaultInputComponent;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.sensor.highlighting.TypeOfText;
+import org.sonar.api.scanner.fs.InputProject;
+import org.sonar.core.util.CloseableIterator;
+import org.sonar.scanner.protocol.output.FileStructure;
+import org.sonar.scanner.protocol.output.ScannerReport;
+import org.sonar.scanner.protocol.output.ScannerReport.Component;
+import org.sonar.scanner.protocol.output.ScannerReport.Symbol;
+import org.sonar.scanner.protocol.output.ScannerReportReader;
+import org.sonar.scanner.report.ScannerReportUtils;
+import org.sonar.scanner.scan.SpringProjectScanContainer;
+import org.sonar.scanner.scan.filesystem.InputComponentStore;
+
+public class AnalysisResult implements AnalysisObserver {
+
+  private static final Logger LOG = LoggerFactory.getLogger(AnalysisResult.class);
+
+  private final Map<String, InputFile> inputFilesByKeys = new HashMap<>();
+  private InputProject project;
+  private ScannerReportReader reader;
+
+  @Override
+  public void analysisCompleted(SpringProjectScanContainer container) {
+    LOG.info("Store analysis results in memory for later assertions in medium test");
+    FileStructure fileStructure = container.getComponentByType(FileStructure.class);
+    reader = new ScannerReportReader(fileStructure);
+    project = container.getComponentByType(InputProject.class);
+
+    storeFs(container);
+
+  }
+
+  public ScannerReportReader getReportReader() {
+    return reader;
+  }
+
+  private void storeFs(SpringProjectScanContainer container) {
+    InputComponentStore inputFileCache = container.getComponentByType(InputComponentStore.class);
+    for (InputFile inputPath : inputFileCache.inputFiles()) {
+      inputFilesByKeys.put(((DefaultInputFile) inputPath).getProjectRelativePath(), inputPath);
+    }
+  }
+
+  public Component getReportComponent(InputComponent inputComponent) {
+    return getReportReader().readComponent(((DefaultInputComponent) inputComponent).scannerId());
+  }
+
+  public Component getReportComponent(int scannerId) {
+    return getReportReader().readComponent(scannerId);
+  }
+
+  public List<ScannerReport.Issue> issuesFor(InputComponent inputComponent) {
+    return readFromReport(inputComponent, ScannerReportReader::readComponentIssues);
+  }
+
+  public List<ScannerReport.ExternalIssue> externalIssuesFor(InputComponent inputComponent) {
+    return readFromReport(inputComponent, ScannerReportReader::readComponentExternalIssues);
+  }
+
+  public List<ScannerReport.Issue> issuesFor(Component reportComponent) {
+    return readFromReport(reportComponent, ScannerReportReader::readComponentIssues);
+  }
+
+  public InputProject project() {
+    return project;
+  }
+
+  public Collection<InputFile> inputFiles() {
+    return inputFilesByKeys.values();
+  }
+
+  @CheckForNull
+  public InputFile inputFile(String relativePath) {
+    return inputFilesByKeys.get(relativePath);
+  }
+
+  public Map<String, List<ScannerReport.Measure>> allMeasures() {
+    Map<String, List<ScannerReport.Measure>> result = new HashMap<>();
+    result.put(project.key(), readFromReport(project, ScannerReportReader::readComponentMeasures));
+    for (InputFile inputFile : inputFilesByKeys.values()) {
+      result.put(inputFile.key(), readFromReport(inputFile, ScannerReportReader::readComponentMeasures));
+    }
+    return result;
+  }
+
+  /**
+   * Get highlighting types at a given position in an inputfile
+   *
+   * @param lineOffset 0-based offset in file
+   */
+  public List<TypeOfText> highlightingTypeFor(InputFile file, int line, int lineOffset) {
+    int ref = ((DefaultInputComponent) file).scannerId();
+    if (!reader.hasSyntaxHighlighting(ref)) {
+      return Collections.emptyList();
+    }
+    TextPointer pointer = file.newPointer(line, lineOffset);
+    List<TypeOfText> result = new ArrayList<>();
+    try (CloseableIterator<ScannerReport.SyntaxHighlightingRule> it = reader.readComponentSyntaxHighlighting(ref)) {
+      while (it.hasNext()) {
+        ScannerReport.SyntaxHighlightingRule rule = it.next();
+        TextRange ruleRange = toRange(file, rule.getRange());
+        if (ruleRange.start().compareTo(pointer) <= 0 && ruleRange.end().compareTo(pointer) > 0) {
+          result.add(ScannerReportUtils.toBatchType(rule.getType()));
+        }
+      }
+    } catch (Exception e) {
+      throw new IllegalStateException("Can't read syntax highlighting for " + file, e);
+    }
+    return result;
+  }
+
+  private static TextRange toRange(InputFile file, ScannerReport.TextRange reportRange) {
+    return file.newRange(file.newPointer(reportRange.getStartLine(), reportRange.getStartOffset()), file.newPointer(reportRange.getEndLine(), reportRange.getEndOffset()));
+  }
+
+  /**
+   * Get list of all start positions of a symbol in an inputfile
+   *
+   * @param symbolStartLine       0-based start offset for the symbol in file
+   * @param symbolStartLineOffset 0-based end offset for the symbol in file
+   */
+  @CheckForNull
+  public List<ScannerReport.TextRange> symbolReferencesFor(InputFile file, int symbolStartLine, int symbolStartLineOffset) {
+    int ref = ((DefaultInputComponent) file).scannerId();
+    try (CloseableIterator<Symbol> symbols = getReportReader().readComponentSymbols(ref)) {
+      while (symbols.hasNext()) {
+        Symbol symbol = symbols.next();
+        if (symbol.getDeclaration().getStartLine() == symbolStartLine && symbol.getDeclaration().getStartOffset() == symbolStartLineOffset) {
+          return symbol.getReferenceList();
+        }
+      }
+    }
+    return Collections.emptyList();
+  }
+
+  public List<ScannerReport.Duplication> duplicationsFor(InputFile file) {
+    return readFromReport(file, ScannerReportReader::readComponentDuplications);
+  }
+
+  public List<ScannerReport.CpdTextBlock> duplicationBlocksFor(InputFile file) {
+    return readFromReport(file, ScannerReportReader::readCpdTextBlocks);
+  }
+
+  @CheckForNull
+  public ScannerReport.LineCoverage coverageFor(InputFile file, int line) {
+    int ref = ((DefaultInputComponent) file).scannerId();
+    try (CloseableIterator<ScannerReport.LineCoverage> it = getReportReader().readComponentCoverage(ref)) {
+      while (it.hasNext()) {
+        ScannerReport.LineCoverage coverage = it.next();
+        if (coverage.getLine() == line) {
+          return coverage;
+        }
+      }
+    } catch (Exception e) {
+      throw new IllegalStateException(e);
+    }
+    return null;
+  }
+
+  public List<ScannerReport.AdHocRule> adHocRules() {
+    return readFromReport(ScannerReportReader::readAdHocRules);
+  }
+
+  @NotNull
+  private <G> List<G> readFromReport(InputComponent component, BiFunction<ScannerReportReader, Integer, CloseableIterator<G>> readerMethod) {
+    int ref = ((DefaultInputComponent) component).scannerId();
+    return readFromReport(r -> readerMethod.apply(r, ref));
+  }
+
+  @NotNull
+  private <G> List<G> readFromReport(Component component, BiFunction<ScannerReportReader, Integer, CloseableIterator<G>> readerMethod) {
+    int ref = component.getRef();
+    return readFromReport(r -> readerMethod.apply(r, ref));
+  }
+
+  @NotNull
+  private <G> List<G> readFromReport(Function<ScannerReportReader, CloseableIterator<G>> readerMethod) {
+    List<G> result = new ArrayList<>();
+    try (CloseableIterator<G> it = readerMethod.apply(getReportReader())) {
+      while (it.hasNext()) {
+        result.add(it.next());
+      }
+    } catch (Exception e) {
+      throw new IllegalStateException(e);
+    }
+    return result;
+  }
+}
diff --git a/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/FakeLanguagesLoader.java b/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/FakeLanguagesLoader.java
new file mode 100644 (file)
index 0000000..f2e2f48
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.util.HashMap;
+import java.util.Map;
+import javax.annotation.Priority;
+import org.sonar.api.resources.Languages;
+import org.sonar.scanner.repository.language.Language;
+import org.sonar.scanner.repository.language.LanguagesLoader;
+import org.sonar.scanner.repository.language.SupportedLanguageDto;
+
+@Priority(1)
+public class FakeLanguagesLoader implements LanguagesLoader {
+
+  private final Map<String, Language> languageMap = new HashMap<>();
+
+  public FakeLanguagesLoader() {
+    languageMap.put("xoo", new Language(new SupportedLanguageDto("xoo", "xoo", new String[] { ".xoo" }, new String[0])));
+  }
+
+  public FakeLanguagesLoader(Languages languages) {
+    for (org.sonar.api.resources.Language language : languages.all()) {
+      languageMap.put(language.getKey(), new Language(new SupportedLanguageDto(language.getKey(), language.getName(), language.getFileSuffixes(), language.filenamePatterns())));
+    }
+  }
+  @Override
+  public Map<String, Language> load() {
+    return languageMap;
+  }
+
+  public void addLanguage(String key, String name, String[] suffixes, String[] filenamePatterns) {
+    languageMap.put(key, new Language(new SupportedLanguageDto(key, name, suffixes, filenamePatterns)));
+  }
+
+}
diff --git a/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/FakeLanguagesProvider.java b/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/FakeLanguagesProvider.java
new file mode 100644 (file)
index 0000000..6009746
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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 javax.annotation.Priority;
+import org.sonar.api.resources.Language;
+import org.sonar.api.resources.Languages;
+import org.springframework.context.annotation.Bean;
+
+@Priority(1)
+public class FakeLanguagesProvider {
+
+  private Languages languages = new Languages();
+
+  @Bean("Languages")
+  public Languages provide() {
+    return this.languages;
+  }
+
+  public void addLanguage(String key, String name, boolean publishAllFiles) {
+    this.languages.add(new FakeLanguage(key, name, publishAllFiles));
+  }
+
+  private static class FakeLanguage implements Language {
+
+    private final String name;
+    private final String key;
+    private final boolean publishAllFiles;
+
+    public FakeLanguage(String key, String name, boolean publishAllFiles) {
+      this.name = name;
+      this.key = key;
+      this.publishAllFiles = publishAllFiles;
+    }
+
+    @Override
+    public String getKey() {
+      return this.key;
+    }
+
+    @Override
+    public String getName() {
+      return this.name;
+    }
+
+    @Override
+    public String[] getFileSuffixes() {
+      return new String[0];
+    }
+
+    @Override
+    public boolean publishAllFiles() {
+      return this.publishAllFiles;
+    }
+  }
+
+
+}
diff --git a/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/FakePluginInstaller.java b/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/FakePluginInstaller.java
new file mode 100644 (file)
index 0000000..a735827
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Priority;
+import org.sonar.api.Plugin;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.plugin.PluginType;
+import org.sonar.scanner.bootstrap.PluginInstaller;
+import org.sonar.scanner.bootstrap.ScannerPlugin;
+
+@Priority(1)
+public class FakePluginInstaller implements PluginInstaller {
+
+  private final Map<String, ScannerPlugin> pluginsByKeys = new HashMap<>();
+  private final List<LocalPlugin> mediumTestPlugins = new ArrayList<>();
+  private final List<LocalPlugin> optionalMediumTestPlugins = new ArrayList<>();
+
+  public FakePluginInstaller add(String pluginKey, File jarFile, long lastUpdatedAt) {
+    pluginsByKeys.put(pluginKey, new ScannerPlugin(pluginKey, lastUpdatedAt, PluginType.BUNDLED, PluginInfo.create(jarFile)));
+    return this;
+  }
+
+  public FakePluginInstaller add(String pluginKey, Plugin instance) {
+    mediumTestPlugins.add(new LocalPlugin(pluginKey, instance, Set.of()));
+    return this;
+  }
+
+  public FakePluginInstaller addOptional(String pluginKey, Set<String> requiredForLanguages, Plugin instance) {
+    optionalMediumTestPlugins.add(new LocalPlugin(pluginKey, instance, requiredForLanguages));
+    return this;
+  }
+
+  @Override
+  public Map<String, ScannerPlugin> installAllPlugins() {
+    return pluginsByKeys;
+  }
+
+  @Override
+  public Map<String, ScannerPlugin> installRequiredPlugins() {
+    return pluginsByKeys;
+  }
+
+  @Override
+  public Map<String, ScannerPlugin> installPluginsForLanguages(Set<String> languageKeys) {
+    return pluginsByKeys;
+  }
+
+  @Override
+  public List<LocalPlugin> installLocals() {
+    return mediumTestPlugins;
+  }
+
+  @Override
+  public List<LocalPlugin> installOptionalLocals(Set<String> languageKeys) {
+    return optionalMediumTestPlugins.stream()
+      .filter(plugin -> languageKeys.stream().anyMatch(lang -> plugin.requiredForLanguages().contains(lang)))
+      .toList();
+  }
+}
diff --git a/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java b/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java
new file mode 100644 (file)
index 0000000..59eb629
--- /dev/null
@@ -0,0 +1,616 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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 java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import javax.annotation.Priority;
+import org.apache.commons.io.FileUtils;
+import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
+import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+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.Rule;
+
+import static java.util.Collections.emptySet;
+
+/**
+ * Main utility class for writing scanner medium tests.
+ */
+public class ScannerMediumTester extends ExternalResource implements BeforeTestExecutionCallback, AfterTestExecutionCallback {
+
+  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 final FakeLanguagesLoader languagesLoader = new FakeLanguagesLoader();
+  private final FakeLanguagesProvider languagesProvider = new FakeLanguagesProvider();
+  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) {
+    pluginInstaller.add(pluginKey, instance);
+    return this;
+  }
+
+  public ScannerMediumTester registerOptionalPlugin(String pluginKey, Set<String> requiredForLanguages, Plugin instance) {
+    pluginInstaller.addOptional(pluginKey, requiredForLanguages, instance);
+    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.setRepo(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
+  public void afterTestExecution(ExtensionContext extensionContext) {
+    after();
+  }
+
+  @Override
+  public void beforeTestExecution(ExtensionContext extensionContext) {
+    before();
+  }
+
+  @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 void addLanguage(String key, String name, String... suffixes) {
+    languagesLoader.addLanguage(key, name, suffixes, new String[0]);
+    languagesProvider.addLanguage(key, name, true);
+  }
+
+  public void addLanguage(String key, String name, boolean publishAllFiles, String... suffixes) {
+    languagesLoader.addLanguage(key, name, suffixes, new String[0]);
+    languagesProvider.addLanguage(key, name, publishAllFiles);
+  }
+
+  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 builder = 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,
+          tester.languagesLoader,
+          tester.languagesProvider,
+          result);
+      if (tester.logOutput != null) {
+        builder.setLogOutput(tester.logOutput);
+      } else {
+        builder.setEnableLoggingConfiguration(false);
+      }
+      builder.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/testFixtures/java/org/sonar/scanner/mediumtest/package-info.java b/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/package-info.java
new file mode 100644 (file)
index 0000000..ec4d264
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.scanner.mediumtest;
+
+import javax.annotation.ParametersAreNonnullByDefault;