//////////////////////////////////////////////////////////////////////////////// // checkstyle: Checks Java source code for adherence to a set of rules. // Copyright (C) 2001-2020 the original author or authors. // // This library 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 library 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 library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //////////////////////////////////////////////////////////////////////////////// package org.checkstyle.plugins.sonar; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.fail; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.stream.Collectors; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.assertj.core.api.Assertions; import org.fest.util.Collections; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.Gson; import com.sonar.orchestrator.Orchestrator; import com.sonar.orchestrator.build.Build; import com.sonar.orchestrator.build.BuildResult; import com.sonar.orchestrator.build.MavenBuild; import com.sonar.orchestrator.container.Edition; import com.sonar.orchestrator.container.Server; import com.sonar.orchestrator.http.HttpMethod; import com.sonar.orchestrator.http.HttpResponse; import com.sonar.orchestrator.locator.FileLocation; import com.sonar.orchestrator.locator.MavenLocation; /** * Integration testing of plugin jar inside of sonar. */ public class RunPluginTest { private static final Logger LOG = LoggerFactory.getLogger(RunPluginTest.class); private static final String SONAR_APP_VERSION = "7.9.2"; private static final int LOGS_NUMBER_LINES = 200; private static final String TRUE = "true"; private static final String PROJECT_KEY = "com.puppycrows.tools:checkstyle"; private static final String PROJECT_NAME = "integration-test-project"; private static final List<String> DEACTIVATED_RULES = Collections.list( "com.puppycrawl.tools.checkstyle.checks.coding.MissingCtorCheck", "com.puppycrawl.tools.checkstyle.checks.design.DesignForExtensionCheck", "com.puppycrawl.tools.checkstyle.checks.imports.ImportControlCheck", "com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocPackageCheck", "com.puppycrawl.tools.checkstyle.checks.javadoc.WriteTagCheck", "com.puppycrawl.tools.checkstyle.checks.UncommentedMainCheck"); private static Orchestrator orchestrator; @Rule public final TemporaryFolder temp = new TemporaryFolder(); @BeforeClass public static void beforeAll() { orchestrator = Orchestrator.builderEnv() .setZipFile(new File("target/temp-downloads/sonar-application-" + SONAR_APP_VERSION + ".zip")) //.setSonarVersion(System.getProperty("sonar.runtimeVersion", SONAR_APP_VERSION)) .setEdition(Edition.COMMUNITY) .addPlugin(FileLocation.byWildcardMavenFilename(new File("target"), "checkstyle-sonar-plugin-*.jar")) .addPlugin(MavenLocation.of("org.sonarsource.sonar-lits-plugin", "sonar-lits-plugin", "0.8.0.1209")) .addPlugin(MavenLocation.of("org.sonarsource.java", "sonar-java-plugin", "6.0.0.20538")) .setServerProperty("sonar.web.javaOpts", "-Xmx1G") .build(); orchestrator.start(); } @AfterClass public static void afterAll() { orchestrator.stop(); } @Test public void testSonarExecution() { try { final MavenBuild build = testProjectBuild(); executeBuildWithCommonProperties(build, true); } catch (IOException exception) { LOG.error("Build execution error.", exception); fail("Failed to execute build."); } } private static void executeBuildWithCommonProperties(Build<?> build, boolean buildQuietly) throws IOException { final String dumpOldLocation = FileLocation.of("target/old/" + PROJECT_NAME) .getFile() .getAbsolutePath(); final String dumpNewLocation = FileLocation.of("target/actual/" + PROJECT_NAME) .getFile() .getAbsolutePath(); build .setProperty("sonar.login", "admin") .setProperty("sonar.password", "admin") .setProperty("sonar.cpd.exclusions", "**/*") .setProperty("sonar.import_unknown_files", TRUE) .setProperty("sonar.skipPackageDesign", TRUE) .setProperty("dump.old", dumpOldLocation) .setProperty("dump.new", dumpNewLocation) .setProperty("lits.differences", litsDifferencesPath()) .setProperty("sonar.java.xfile", TRUE) .setProperty("sonar.java.failOnException", TRUE); final BuildResult buildResult; // if build fail, job is not violently interrupted, allowing time to dump SQ logs if (buildQuietly) { buildResult = orchestrator.executeBuildQuietly(build); } else { buildResult = orchestrator.executeBuild(build); } if (buildResult.isSuccess()) { assertNoDifferences(); } else { dumpServerLogs(); fail("Build failure for project: " + PROJECT_NAME); } } private static void assertNoDifferences() { try { final String differences = new String(Files .readAllBytes(new File(litsDifferencesPath()).toPath()), UTF_8); Assertions.assertThat(differences) .isEmpty(); } catch (IOException exception) { LOG.error("Failed to read LITS differences.", exception); fail("LITS differences not computed."); } } private static String litsDifferencesPath() { return FileLocation.of("target/" + PROJECT_NAME + "_differences") .getFile() .getAbsolutePath(); } private static void dumpServerLogs() throws IOException { final Server server = orchestrator.getServer(); LOG.error(":::::::::::::::: DUMPING SERVER LOGS ::::::::::::::::"); dumpServerLogLastLines(server.getAppLogs()); dumpServerLogLastLines(server.getCeLogs()); dumpServerLogLastLines(server.getEsLogs()); dumpServerLogLastLines(server.getWebLogs()); } private static void dumpServerLogLastLines(File logFile) throws IOException { if (logFile.exists()) { List<String> logs = Files.readAllLines(logFile.toPath()); final int nbLines = logs.size(); if (nbLines > LOGS_NUMBER_LINES) { logs = logs.subList(nbLines - LOGS_NUMBER_LINES, nbLines); } final String collectedLogs = logs.stream() .collect(Collectors.joining(System.lineSeparator())); LOG.error("============= START {} =============", logFile.getName()); LOG.error("{} {}", System.lineSeparator(), collectedLogs); LOG.error("============= END {} =============", logFile.getName()); } } private MavenBuild testProjectBuild() throws IOException { final File targetDir = prepareProject(); final String pomLocation = targetDir.getCanonicalPath() + "/pom.xml"; final File pomFile = FileLocation.of(pomLocation) .getFile() .getCanonicalFile(); final MavenBuild mavenBuild = MavenBuild.create() .setPom(pomFile) .setCleanPackageSonarGoals() .addArgument("-Dmaven.test.skip=true") .addArgument("-DskipTests") .addArgument("-DskipITs"); mavenBuild.setProperty("sonar.projectKey", PROJECT_KEY); return mavenBuild; } @SuppressWarnings("unchecked") private File prepareProject() throws IOException { // set severities of all active rules to INFO final String profilesResponse = orchestrator.getServer() .newHttpCall("api/qualityprofiles/create") .setAdminCredentials() .setMethod(HttpMethod.POST) .setParam("language", "java") .setParam("name", "checkstyle") .execute() .getBodyAsString(); final Map<String, Object> map = new Gson().fromJson(profilesResponse, Map.class); final String profileKey = ((Map<String, String>) map.get("profile")).get("key"); if (StringUtils.isEmpty(profileKey)) { fail("Could not retrieve profile key: setting up quality profile failed."); } else { final HttpResponse activateRulesResponse = orchestrator.getServer() .newHttpCall("api/qualityprofiles/activate_rules") .setAdminCredentials() .setMethod(HttpMethod.POST) .setParam("activation_severity", "INFO") .setParam("languages", "java") .setParam("profile_key", profileKey) .setParam("repositories", "checkstyle") .executeUnsafely(); if (!activateRulesResponse.isSuccessful()) { fail(String.format(Locale.ROOT, "Failed to activate all rules. %s", activateRulesResponse.getBodyAsString())); } // deactivate some rules for test project for (String ruleKey : DEACTIVATED_RULES) { final HttpResponse deactivateRulesResponse = orchestrator.getServer() .newHttpCall("api/qualityprofiles/deactivate_rule") .setAdminCredentials() .setMethod(HttpMethod.POST) .setParam("rule_key", "checkstyle:" + ruleKey) .setParam("profile_key", profileKey) .executeUnsafely(); if (!deactivateRulesResponse.isSuccessful()) { fail(String.format(Locale.ROOT, "Failed to deactivate rule %s. %s", ruleKey, deactivateRulesResponse.getBodyAsString())); } } } // associate CS profile orchestrator.getServer().provisionProject(PROJECT_KEY, PROJECT_NAME); final HttpResponse assignQpResponse = orchestrator.getServer() .newHttpCall("api/qualityprofiles/add_project") .setAdminCredentials() .setMethod(HttpMethod.POST) .setParam("language", "java") .setParam("profileName", "checkstyle") .setParam("projectKey", PROJECT_KEY) .executeUnsafely(); if (!assignQpResponse.isSuccessful()) { fail(String.format(Locale.ROOT, "Failed to add project to quality profile. %s", assignQpResponse.getBodyAsString())); } // copy project to analysis space final Path projectRoot = Paths.get("src/it/resources/" + PROJECT_NAME); final File targetDir = temp.newFolder(PROJECT_NAME); FileUtils.copyDirectory(projectRoot.toFile(), targetDir); return targetDir; } }