/* * 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 util; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableMap; import com.sonar.orchestrator.Orchestrator; import com.sonar.orchestrator.build.SonarRunner; import java.util.HashSet; import java.util.Set; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import org.junit.rules.ExternalResource; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static java.util.Objects.requireNonNull; import static util.ItUtils.resetSettings; /** * Rule wrapping around an {@link Orchestrator} instance which handle: * <ul> * <li>automatic reset of Orchestrator data after each method when used as a {@link org.junit.Rule}, * after each class when used as a {@link org.junit.ClassRule}</li> * <li>automatic reset of server properties after each method when used as a {@link org.junit.Rule}, * after each class when used as a {@link org.junit.ClassRule}</li> * <li>associating project with a specific Quality Profile before running an analysis</li> * <li>provisioning a project before its first analysis so that a Quality Profile can be associated to it</li> * <li>"restoring" a Quality Profile before an analysis with a specific Quality Profile</li> * </ul> * * This Rule has preparatory methods ({@link #registerProfile(String)} and {@link #registerProject(String)}) which * will allow consequent calls to the rule methods to be based solely on Quality Profile and Project keys. In addition, * these methods returns the Quality Profile and Project key to avoid information duplication (the only magic string * the IT developer has to know is the relative path of the Project or the Quality Profile). * * To run an analysis, use method {@link #newProjectAnalysis(String)} to create a {@link ProjectAnalysis} * object. This object has a {@link ProjectAnalysis#run()} method which will start the analysis. * {@link ProjectAnalysis} can safely be reused to run the same analysis multiple times. In addition, these objects are * immutable. Any call to one of their method which would modify their state will create a new instance which can also * be reused at will. */ public class ProjectAnalysisRule extends ExternalResource { private final Orchestrator orchestrator; private final LoadedProfiles loadedProfiles = new LoadedProfiles(); private final LoadedProjects loadedProjects = new LoadedProjects(); private final Set<String> serverProperties = new HashSet<>(); private ProjectAnalysisRule(Orchestrator orchestrator) { this.orchestrator = orchestrator; } public static ProjectAnalysisRule from(Orchestrator orchestrator) { return new ProjectAnalysisRule(requireNonNull(orchestrator, "Orchestrator instance can not be null")); } /** * @param relativePathToProfile eg.: "/issue/suite/IssueFilterExtensionTest/xoo-with-many-rules.xml" * * @return the quality profile key */ public String registerProfile(String relativePathToProfile) { return this.loadedProfiles.loadProfile(relativePathToProfile); } /** * @param projectRelativePath path relative to it/it-projects, eg. "shared/xoo-multi-modules-sample" * * @return the project key */ public String registerProject(String projectRelativePath) { return this.loadedProjects.load(projectRelativePath); } public ProjectAnalysis newProjectAnalysis(String projectKey) { ProjectState projectState = this.loadedProjects.getProjectState(projectKey); return new ProjectAnalysisImpl(projectState, null, false); } @Override protected void before() throws Throwable { orchestrator.resetData(); } @Override protected void after() { resetServerProperties(); resetRuleState(); } private void resetServerProperties() { resetSettings(orchestrator, null, serverProperties.toArray(new String[] {})); } public void setServerPropertyImpl(String key, @Nullable String value) { ItUtils.setServerProperty(orchestrator, key, value); } public ProjectAnalysisRule setServerProperty(String key, String value) { setServerPropertyImpl(key, value); this.serverProperties.add(key); return this; } @Immutable private final class ProjectAnalysisImpl implements ProjectAnalysis { private final ProjectState projectState; @CheckForNull private final Profile qualityProfile; private final boolean debugLogs; @CheckForNull private final String[] properties; private ProjectAnalysisImpl(ProjectState projectState, @Nullable Profile qualityProfile, boolean debugLogs, String... properties) { this.projectState = projectState; this.qualityProfile = qualityProfile; this.debugLogs = debugLogs; this.properties = properties; } @Override public ProjectAnalysis withQualityProfile(String qualityProfileKey) { checkNotNull(qualityProfileKey, "Specified Quality Profile Key can not be null"); if (this.qualityProfile != null && this.qualityProfile.getProfileKey().equals(qualityProfileKey)) { return this; } return new ProjectAnalysisImpl(this.projectState, loadedProfiles.getState(qualityProfileKey), this.debugLogs, this.properties); } @Override public ProjectAnalysis withXooEmptyProfile() { if (this.qualityProfile == Profile.XOO_EMPTY_PROFILE) { return this; } return new ProjectAnalysisImpl(this.projectState, Profile.XOO_EMPTY_PROFILE, this.debugLogs, this.properties); } @Override public ProjectAnalysis withDebugLogs(boolean enabled) { if (this.debugLogs == enabled) { return this; } return new ProjectAnalysisImpl(this.projectState, this.qualityProfile, enabled, this.properties); } @Override public ProjectAnalysis withProperties(String... properties) { checkArgument( properties == null || properties.length % 2 == 0, "there must be an even number of String parameters (got %s): key/value pairs must be complete", properties == null ? 0 : properties.length); return new ProjectAnalysisImpl(this.projectState, this.qualityProfile, this.debugLogs, properties); } @Override public void run() { provisionIfNecessary(); setQualityProfileIfNecessary(); runAnalysis(); } private void setQualityProfileIfNecessary() { if (this.qualityProfile != null) { if (this.qualityProfile != Profile.XOO_EMPTY_PROFILE) { ItUtils.restoreProfile(orchestrator, getClass().getResource(this.qualityProfile.getRelativePath())); } orchestrator.getServer().associateProjectToQualityProfile( this.projectState.getProjectKey(), this.qualityProfile.getLanguageKey(), this.qualityProfile.getProfileKey()); } } private void provisionIfNecessary() { if (this.qualityProfile != null && !projectState.isProvisioned()) { String projectKey = projectState.getProjectKey(); orchestrator.getServer().provisionProject(projectKey, MoreObjects.firstNonNull(projectState.getProjectName(), projectKey)); projectState.setProvisioned(true); } } private SonarRunner runAnalysis() { SonarRunner sonarRunner = SonarRunner.create(projectState.getProjectDir()); ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); for (int i = 0; i < this.properties.length; i += 2) { builder.put(this.properties[i], this.properties[i + 1]); } SonarRunner scan = sonarRunner.setDebugLogs(this.debugLogs).setProperties(builder.build()); orchestrator.executeBuild(scan); return scan; } } private void resetRuleState() { this.loadedProjects.reset(); this.loadedProfiles.reset(); this.serverProperties.clear(); } }