/* * SonarQube * Copyright (C) 2009-2017 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.HashBasedTable; import com.google.common.collect.Table; 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.Properties; import java.util.function.Consumer; import javax.annotation.Nullable; import org.apache.commons.io.FileUtils; import org.sonar.api.CoreProperties; import org.sonar.api.Plugin; import org.sonar.api.batch.debt.internal.DefaultDebtModel; 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.batch.bootstrapper.Batch; import org.sonar.batch.bootstrapper.EnvironmentInformation; import org.sonar.batch.bootstrapper.IssueListener; import org.sonar.batch.bootstrapper.LogOutput; import org.sonar.scanner.bootstrap.GlobalMode; import org.sonar.scanner.issue.tracking.ServerLineHashesLoader; import org.sonar.scanner.protocol.input.ScannerInput.ServerIssue; import org.sonar.scanner.report.ReportPublisher; import org.sonar.scanner.repository.FileData; import org.sonar.scanner.repository.MetricsRepository; import org.sonar.scanner.repository.MetricsRepositoryLoader; import org.sonar.scanner.repository.ProjectRepositories; import org.sonar.scanner.repository.ProjectRepositoriesLoader; import org.sonar.scanner.repository.QualityProfileLoader; import org.sonar.scanner.repository.ServerIssuesLoader; import org.sonar.scanner.repository.settings.SettingsLoader; import org.sonar.scanner.rule.ActiveRulesLoader; import org.sonar.scanner.rule.LoadedActiveRule; import org.sonar.scanner.rule.RulesLoader; import org.sonarqube.ws.QualityProfiles.SearchWsResponse.QualityProfile; import org.sonarqube.ws.Rules.ListResponse.Rule; /** * Main utility class for writing scanner medium tests. * */ public class ScannerMediumTester { private Batch batch; private static Path workingDir = null; private static Path globalWorkingDir = null; private static void createWorkingDirs() throws IOException { destroyWorkingDirs(); workingDir = java.nio.file.Files.createTempDirectory("mediumtest-working-dir"); globalWorkingDir = java.nio.file.Files.createTempDirectory("mediumtest-global-working-dir"); } private static void destroyWorkingDirs() throws IOException { if (workingDir != null) { FileUtils.deleteDirectory(workingDir.toFile()); workingDir = null; } if (globalWorkingDir != null) { FileUtils.deleteDirectory(globalWorkingDir.toFile()); globalWorkingDir = null; } } public static BatchMediumTesterBuilder builder() { try { createWorkingDirs(); } catch (IOException e) { e.printStackTrace(); } BatchMediumTesterBuilder builder = new BatchMediumTesterBuilder().registerCoreMetrics(); builder.bootstrapProperties.put(GlobalMode.MEDIUM_TEST_ENABLED, "true"); builder.bootstrapProperties.put(ReportPublisher.KEEP_REPORT_PROP_KEY, "true"); builder.bootstrapProperties.put(CoreProperties.WORKING_DIRECTORY, workingDir.toString()); builder.bootstrapProperties.put("sonar.userHome", globalWorkingDir.toString()); return builder; } public static class BatchMediumTesterBuilder { private final FakeMetricsRepositoryLoader globalRefProvider = new FakeMetricsRepositoryLoader(); private final FakeProjectRepositoriesLoader projectRefProvider = new FakeProjectRepositoriesLoader(); private final FakePluginInstaller pluginInstaller = new FakePluginInstaller(); private final FakeServerIssuesLoader serverIssues = new FakeServerIssuesLoader(); private final FakeServerLineHashesLoader serverLineHashes = new FakeServerLineHashesLoader(); private final Map<String, String> bootstrapProperties = new HashMap<>(); private final FakeRulesLoader rulesLoader = new FakeRulesLoader(); private final FakeQualityProfileLoader qualityProfiles = new FakeQualityProfileLoader(); private final FakeActiveRulesLoader activeRules = new FakeActiveRulesLoader(); private boolean associated = true; private LogOutput logOutput = null; public ScannerMediumTester build() { return new ScannerMediumTester(this); } public BatchMediumTesterBuilder setAssociated(boolean associated) { this.associated = associated; return this; } public BatchMediumTesterBuilder setLogOutput(LogOutput logOutput) { this.logOutput = logOutput; return this; } public BatchMediumTesterBuilder registerPlugin(String pluginKey, File location) { pluginInstaller.add(pluginKey, location); return this; } public BatchMediumTesterBuilder registerPlugin(String pluginKey, Plugin instance) { pluginInstaller.add(pluginKey, instance); return this; } public BatchMediumTesterBuilder registerCoreMetrics() { for (Metric<?> m : CoreMetrics.getMetrics()) { registerMetric(m); } return this; } public BatchMediumTesterBuilder registerMetric(Metric<?> metric) { globalRefProvider.add(metric); return this; } public BatchMediumTesterBuilder addQProfile(String language, String name) { qualityProfiles.add(language, name); return this; } public BatchMediumTesterBuilder addRule(Rule rule) { rulesLoader.addRule(rule); return this; } public BatchMediumTesterBuilder addRule(String key, String repoKey, String internalKey, String name) { Rule.Builder builder = Rule.newBuilder(); builder.setKey(key); builder.setRepository(repoKey); if (internalKey != null) { builder.setInternalKey(internalKey); } builder.setName(name); rulesLoader.addRule(builder.build()); return this; } public BatchMediumTesterBuilder addRules(RulesDefinition rulesDefinition) { RulesDefinition.Context context = new RulesDefinition.Context(); rulesDefinition.define(context); List<Repository> repositories = context.repositories(); for (Repository repo : repositories) { for (RulesDefinition.Rule rule : repo.rules()) { this.addRule(rule.key(), rule.repository().key(), rule.internalKey(), rule.name()); } } return this; } public BatchMediumTesterBuilder addDefaultQProfile(String language, String name) { addQProfile(language, name); return this; } public BatchMediumTesterBuilder setPreviousAnalysisDate(Date previousAnalysis) { projectRefProvider.setLastAnalysisDate(previousAnalysis); return this; } public BatchMediumTesterBuilder bootstrapProperties(Map<String, String> props) { bootstrapProperties.putAll(props); return this; } public BatchMediumTesterBuilder activateRule(LoadedActiveRule activeRule) { activeRules.addActiveRule(activeRule); return this; } public BatchMediumTesterBuilder addActiveRule(String repositoryKey, String ruleKey, @Nullable String templateRuleKey, String name, @Nullable String severity, @Nullable String internalKey, @Nullable String languag) { LoadedActiveRule r = new LoadedActiveRule(); r.setInternalKey(internalKey); r.setRuleKey(RuleKey.of(repositoryKey, ruleKey)); r.setName(name); r.setTemplateRuleKey(templateRuleKey); r.setLanguage(languag); r.setSeverity(severity); activeRules.addActiveRule(r); return this; } public BatchMediumTesterBuilder addFileData(String moduleKey, String path, FileData fileData) { projectRefProvider.addFileData(moduleKey, path, fileData); return this; } public BatchMediumTesterBuilder setLastBuildDate(Date d) { projectRefProvider.setLastAnalysisDate(d); return this; } public BatchMediumTesterBuilder mockServerIssue(ServerIssue issue) { serverIssues.getServerIssues().add(issue); return this; } public BatchMediumTesterBuilder mockLineHashes(String fileKey, String[] lineHashes) { serverLineHashes.byKey.put(fileKey, lineHashes); return this; } } public void start() { batch.start(); } public void stop() { batch.stop(); try { destroyWorkingDirs(); } catch (IOException e) { e.printStackTrace(); } } public void syncProject(String projectKey) { batch.syncProject(projectKey); } private ScannerMediumTester(BatchMediumTesterBuilder builder) { Batch.Builder batchBuilder = Batch.builder() .setEnableLoggingConfiguration(true) .addComponents( new EnvironmentInformation("mediumTest", "1.0"), builder.pluginInstaller, builder.globalRefProvider, builder.qualityProfiles, builder.rulesLoader, builder.projectRefProvider, builder.activeRules, new DefaultDebtModel(), new FakeSettingsLoader()) .setBootstrapProperties(builder.bootstrapProperties) .setLogOutput(builder.logOutput); if (builder.associated) { batchBuilder.addComponents( builder.serverIssues); } batch = batchBuilder.build(); } public TaskBuilder newTask() { return new TaskBuilder(this); } public TaskBuilder newScanTask(File sonarProps) { Properties prop = new Properties(); try (Reader reader = new InputStreamReader(new FileInputStream(sonarProps), StandardCharsets.UTF_8)) { prop.load(reader); } catch (Exception e) { throw new IllegalStateException("Unable to read configuration file", e); } TaskBuilder builder = new TaskBuilder(this); builder.property("sonar.projectBaseDir", sonarProps.getParentFile().getAbsolutePath()); for (Map.Entry<Object, Object> entry : prop.entrySet()) { builder.property(entry.getKey().toString(), entry.getValue().toString()); } return builder; } public static class TaskBuilder { private final Map<String, String> taskProperties = new HashMap<>(); private ScannerMediumTester tester; private IssueListener issueListener = null; public TaskBuilder(ScannerMediumTester tester) { this.tester = tester; } public TaskResult start() { TaskResult result = new TaskResult(); Map<String, String> props = new HashMap<>(); props.putAll(taskProperties); if (issueListener != null) { tester.batch.executeTask(props, result, issueListener); } else { tester.batch.executeTask(props, result); } return result; } public TaskBuilder properties(Map<String, String> props) { taskProperties.putAll(props); return this; } public TaskBuilder property(String key, String value) { taskProperties.put(key, value); return this; } public TaskBuilder setIssueListener(IssueListener issueListener) { this.issueListener = issueListener; return this; } } private static class FakeRulesLoader implements RulesLoader { private List<org.sonarqube.ws.Rules.ListResponse.Rule> rules = new LinkedList<>(); public FakeRulesLoader addRule(Rule rule) { rules.add(rule); return this; } @Override public List<Rule> load() { return rules; } } private static class FakeActiveRulesLoader implements ActiveRulesLoader { private List<LoadedActiveRule> activeRules = new LinkedList<>(); public void addActiveRule(LoadedActiveRule activeRule) { this.activeRules.add(activeRule); } @Override public List<LoadedActiveRule> load(String qualityProfileKey) { return activeRules; } } 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.setId(metricId++); metrics.add(metric); metricId++; return this; } } private static class FakeProjectRepositoriesLoader implements ProjectRepositoriesLoader { private Table<String, String, FileData> fileDataTable = HashBasedTable.create(); private Date lastAnalysisDate; @Override public ProjectRepositories load(String projectKey, boolean isIssuesMode) { Table<String, String, String> settings = HashBasedTable.create(); return new ProjectRepositories(settings, fileDataTable, lastAnalysisDate); } public FakeProjectRepositoriesLoader addFileData(String moduleKey, String path, FileData fileData) { fileDataTable.put(moduleKey, path, fileData); return this; } public FakeProjectRepositoriesLoader setLastAnalysisDate(Date d) { lastAnalysisDate = d; return this; } } private static class FakeQualityProfileLoader implements QualityProfileLoader { private List<QualityProfile> qualityProfiles = new LinkedList<>(); public void add(String language, String name) { qualityProfiles.add(QualityProfile.newBuilder() .setLanguage(language) .setKey(name) .setName(name) .setRulesUpdatedAt(DateUtils.formatDateTime(new Date(1234567891212L))) .build()); } @Override public List<QualityProfile> load(String projectKey, String profileName) { return qualityProfiles; } @Override public List<QualityProfile> loadDefault(String profileName) { return qualityProfiles; } } private static class FakeServerIssuesLoader implements ServerIssuesLoader { private List<ServerIssue> serverIssues = new ArrayList<>(); public List<ServerIssue> getServerIssues() { return serverIssues; } @Override public void load(String componentKey, Consumer<ServerIssue> consumer) { for (ServerIssue serverIssue : serverIssues) { consumer.accept(serverIssue); } } } private static class FakeSettingsLoader implements SettingsLoader { @Override public Map<String, String> load(String componentKey) { return Collections.emptyMap(); } } private static class FakeServerLineHashesLoader implements ServerLineHashesLoader { private Map<String, String[]> byKey = new HashMap<>(); @Override public String[] getLineHashes(String fileKey) { if (byKey.containsKey(fileKey)) { return byKey.get(fileKey); } else { throw new IllegalStateException("You forgot to mock line hashes for " + fileKey); } } } }