org.sonar.java.it.JavaRulingTest.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.java.it.JavaRulingTest.java

Source

/*
 * SonarQube Java
 * Copyright (C) 2013-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.java.it;

import com.google.common.base.Splitter;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
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.build.SonarScanner;
import com.sonar.orchestrator.container.Server;
import com.sonar.orchestrator.locator.FileLocation;
import no.finn.lambdacompanion.Try;
import org.apache.commons.lang.StringUtils;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.Fail;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.wsclient.SonarClient;

import javax.annotation.Nullable;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class JavaRulingTest {

    private static final int LOGS_NUMBER_LINES = 200;
    private static final Logger LOG = LoggerFactory.getLogger(JavaRulingTest.class);

    // by default all rules are enabled, if you want to enable just a subset of rules you can specify the list of
    // rule keys from the command line using "rules" property, i.e. mvn test -Drules=S100,S101
    private static final ImmutableSet<String> SUBSET_OF_ENABLED_RULES = ImmutableSet
            .copyOf(Splitter.on(',').trimResults().omitEmptyStrings().splitToList(System.getProperty("rules", "")));

    @ClassRule
    public static TemporaryFolder TMP_DUMP_OLD_FOLDER = new TemporaryFolder();

    private static Path effectiveDumpOldFolder;

    @ClassRule
    public static Orchestrator orchestrator = Orchestrator.builderEnv()
            .addPlugin(FileLocation.byWildcardMavenFilename(new File("../../sonar-java-plugin/target"),
                    "sonar-java-plugin-*.jar"))
            .setOrchestratorProperty("litsVersion", "0.6").addPlugin("lits").build();
    private static final Gson GSON = new Gson();

    @BeforeClass
    public static void prepare_quality_profiles() {
        ImmutableMap<String, ImmutableMap<String, String>> rulesParameters = ImmutableMap
                .<String, ImmutableMap<String, String>>builder()
                .put("IndentationCheck", ImmutableMap.of("indentationLevel", "4"))
                .put("S1451", ImmutableMap.of("headerFormat", "\n/*\n"
                        + " * Copyright (c) 1998, 2006, Oracle and/or its affiliates. All rights reserved.\n"
                        + " * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms."))
                .build();
        ImmutableSet<String> disabledRules = ImmutableSet.of("CallToDeprecatedMethod", "CycleBetweenPackages",
                // disable because it generates too many issues, performance reasons
                "LeftCurlyBraceStartLineCheck");
        Set<String> activatedRuleKeys = new HashSet<>();
        ProfileGenerator.generate(orchestrator, "java", "squid", rulesParameters, disabledRules,
                SUBSET_OF_ENABLED_RULES, activatedRuleKeys);
        instantiateTemplateRule("S2253", "stringToCharArray",
                "className=\"java.lang.String\";methodName=\"toCharArray\"", activatedRuleKeys);
        instantiateTemplateRule("S4011", "longDate", "className=\"java.util.Date\";argumentTypes=\"long\"",
                activatedRuleKeys);
        instantiateTemplateRule("ArchitecturalConstraint", "doNotUseJavaIoFile",
                "fromClasses=\"**\";toClasses=\"java.io.File\"", activatedRuleKeys);
        instantiateTemplateRule("S124", "commentRegexTest",
                "regularExpression=\"(?i).*TODO\\(user\\).*\";message=\"bad user\"", activatedRuleKeys);
        instantiateTemplateRule("S3417", "doNotUseCommonsCollections", "dependencyName=\"commons-collections:*\";",
                activatedRuleKeys);
        instantiateTemplateRule("S3417", "doNotUseJunitBefore4",
                "dependencyName=\"junit:junit\";version=\"*-3.9.9\"", activatedRuleKeys);
        instantiateTemplateRule("S3546", "InstancesOfNewControllerClosedWithDone",
                "factoryMethod=\"org.sonar.api.server.ws.WebService$Context#createController\";closingMethod=\"org.sonar.api.server.ws.WebService$NewController#done\"",
                activatedRuleKeys);
        instantiateTemplateRule("S3546", "JsonWriterNotClosed",
                "factoryMethod=\"org.sonar.api.server.ws.Response#newJsonWriter\";closingMethod=\"org.sonar.api.utils.text.JsonWriter#close\"",
                activatedRuleKeys);

        SUBSET_OF_ENABLED_RULES.stream().filter(ruleKey -> !activatedRuleKeys.contains(ruleKey))
                .forEach(ruleKey -> Fail.fail("Specified rule does not exist: " + ruleKey));

        prepareDumpOldFolder();
    }

    private static void prepareDumpOldFolder() {
        Path allRulesFolder = Paths.get("src/test/resources");
        if (SUBSET_OF_ENABLED_RULES.isEmpty()) {
            effectiveDumpOldFolder = allRulesFolder.toAbsolutePath();
        } else {
            effectiveDumpOldFolder = TMP_DUMP_OLD_FOLDER.getRoot().toPath().toAbsolutePath();
            Try.of(() -> Files.list(allRulesFolder)).orElseThrow(Throwables::propagate)
                    .filter(p -> p.toFile().isDirectory()).forEach(srcProjectDir -> copyDumpSubset(srcProjectDir,
                            effectiveDumpOldFolder.resolve(srcProjectDir.getFileName())));
        }
    }

    private static void copyDumpSubset(Path srcProjectDir, Path dstProjectDir) {
        Try.of(() -> Files.createDirectory(dstProjectDir)).orElseThrow(Throwables::propagate);
        SUBSET_OF_ENABLED_RULES.stream().map(ruleKey -> srcProjectDir.resolve("squid-" + ruleKey + ".json"))
                .filter(p -> p.toFile().exists())
                .forEach(srcJsonFile -> Try.of(() -> Files.copy(srcJsonFile,
                        dstProjectDir.resolve(srcJsonFile.getFileName()), StandardCopyOption.REPLACE_EXISTING))
                        .orElseThrow(Throwables::propagate));
    }

    @Test
    public void guava() throws Exception {
        test_project("com.google.guava:guava", "guava");
    }

    @Test
    public void apache_commons_beanutils() throws Exception {
        test_project("commons-beanutils:commons-beanutils", "commons-beanutils");
    }

    @Test
    public void fluent_http() throws Exception {
        test_project("net.code-story:http", "fluent-http");
    }

    @Test
    public void java_squid() throws Exception {
        // sonar-java/java-squid (v3.6)
        test_project("org.sonarsource.java:java-squid", "java-squid");
    }

    @Test
    public void sonarqube_server() throws Exception {
        // sonarqube/server/sonar-server (v.5.1.2)
        test_project("org.codehaus.sonar:sonar-server", "sonarqube/server", "sonar-server");
    }

    @Test
    public void jboss_ejb3_tutorial() throws Exception {
        // https://github.com/jbossejb3/jboss-ejb3-tutorial (18/01/2015)
        String projectName = "jboss-ejb3-tutorial";
        prepareProject(projectName, projectName);
        SonarScanner build = SonarScanner.create(FileLocation.of("../sources/jboss-ejb3-tutorial").getFile())
                .setProjectKey(projectName).setProjectName(projectName).setProjectVersion("0.1.0-SNAPSHOT")
                .setSourceEncoding("UTF-8").setSourceDirs(".").setDebugLogs(true)
                .setProperty("sonar.java.source", "1.5");
        executeDebugBuildWithCommonProperties(build, projectName);
    }

    /**
     * Relevant to test lack of semantic, because we don't construct semantic for files in java/lang package.
     */
    @Test
    public void jdk_1_6_source() throws Exception {
        String projectName = "jdk6";
        prepareProject(projectName, projectName);
        SonarScanner build = SonarScanner.create(FileLocation.of("../sources/jdk6").getFile())
                .setProjectKey(projectName).setProjectName(projectName).setProjectVersion("0.1.0-SNAPSHOT")
                .setSourceEncoding("UTF-8").setSourceDirs(".").setProperty("sonar.java.source", "1.5")
                .setProperty("sonar.inclusions", "java/**/*.java");
        executeBuildWithCommonProperties(build, projectName);
    }

    private static void test_project(String projectKey, String projectName) throws IOException {
        test_project(projectKey, null, projectName);
    }

    private static void test_project(String projectKey, @Nullable String path, String projectName)
            throws IOException {
        String pomLocation = "../sources/" + (path != null ? path + "/" : "") + projectName + "/pom.xml";
        File pomFile = FileLocation.of(pomLocation).getFile();
        prepareProject(projectKey, projectName);
        MavenBuild mavenBuild = MavenBuild.create().setPom(pomFile).setCleanPackageSonarGoals()
                .addArgument("-DskipTests");
        executeBuildWithCommonProperties(mavenBuild, projectName);
    }

    private static void prepareProject(String projectKey, String projectName) {
        orchestrator.getServer().provisionProject(projectKey, projectName);
        orchestrator.getServer().associateProjectToQualityProfile(projectKey, "java", "rules");
    }

    private static void executeDebugBuildWithCommonProperties(Build<?> build, String projectName)
            throws IOException {
        executeBuildWithCommonProperties(build, projectName, true);
    }

    private static void executeBuildWithCommonProperties(Build<?> build, String projectName) throws IOException {
        executeBuildWithCommonProperties(build, projectName, false);
    }

    private static void executeBuildWithCommonProperties(Build<?> build, String projectName, boolean buildQuietly)
            throws IOException {
        build.setProperty("sonar.cpd.skip", "true").setProperty("sonar.import_unknown_files", "true")
                .setProperty("sonar.skipPackageDesign", "true").setProperty("sonar.analysis.mode", "preview")
                .setProperty("sonar.issuesReport.html.enable", "true")
                .setProperty("sonar.issuesReport.html.location", htmlReportPath(projectName))
                .setProperty("dump.old", effectiveDumpOldFolder.resolve(projectName).toString())
                .setProperty("dump.new",
                        FileLocation.of("target/actual/" + projectName).getFile().getAbsolutePath())
                .setProperty("lits.differences", litsDifferencesPath(projectName));
        BuildResult buildResult;
        if (buildQuietly) {
            // if build fail, ruling job is not violently interrupted, allowing time to dump SQ logs
            buildResult = orchestrator.executeBuildQuietly(build);
        } else {
            buildResult = orchestrator.executeBuild(build);
        }
        if (buildResult.isSuccess()) {
            assertNoDifferences(projectName);
        } else {
            dumpServerLogs();
        }
    }

    private static void dumpServerLogs() throws IOException {
        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 {
        List<String> logs = Files.readAllLines(logFile.toPath());
        int nbLines = logs.size();
        if (nbLines > LOGS_NUMBER_LINES) {
            logs = logs.subList(nbLines - LOGS_NUMBER_LINES, nbLines);
        }
        LOG.error("=================================== START " + logFile.getName()
                + " ===================================");
        LOG.error(System.lineSeparator() + logs.stream().collect(Collectors.joining(System.lineSeparator())));
        LOG.error("===================================== END " + logFile.getName()
                + " ===================================");
    }

    private static String litsDifferencesPath(String projectName) {
        return FileLocation.of("target/" + projectName + "_differences").getFile().getAbsolutePath();
    }

    private static String htmlReportPath(String projectName) {
        return FileLocation.of("target/" + projectName + "_issue-report").getFile().getAbsolutePath();
    }

    private static void assertNoDifferences(String projectName) throws IOException {
        String differences = new String(Files.readAllBytes(Paths.get(litsDifferencesPath(projectName))),
                StandardCharsets.UTF_8);
        Assertions.assertThat(differences)
                .overridingErrorMessage(
                        differences + " -> file://" + htmlReportPath(projectName) + "/issues-report.html")
                .isEmpty();
    }

    private static void instantiateTemplateRule(String ruleTemplateKey, String instantiationKey, String params,
            Set<String> activatedRuleKeys) {
        if (!SUBSET_OF_ENABLED_RULES.isEmpty() && !SUBSET_OF_ENABLED_RULES.contains(instantiationKey)) {
            return;
        }
        activatedRuleKeys.add(instantiationKey);
        SonarClient sonarClient = orchestrator.getServer().adminWsClient();
        sonarClient.post("/api/rules/create", ImmutableMap.<String, Object>builder().put("name", instantiationKey)
                .put("markdown_description", instantiationKey).put("severity", "INFO").put("status", "READY")
                .put("template_key", "squid:" + ruleTemplateKey).put("custom_key", instantiationKey)
                .put("prevent_reactivation", "true").put("params", "name=\"" + instantiationKey + "\";key=\""
                        + instantiationKey + "\";markdown_description=\"" + instantiationKey + "\";" + params)
                .build());
        String post = sonarClient.get("api/qualityprofiles/search");
        String profileKey = null;
        Map map = GSON.fromJson(post, Map.class);
        for (Map qp : ((List<Map>) map.get("profiles"))) {
            if ("rules".equals(qp.get("name"))) {
                profileKey = (String) qp.get("key");
                break;
            }
        }
        if (StringUtils.isEmpty(profileKey)) {
            LOG.error("Could not retrieve profile key : Template rule " + ruleTemplateKey
                    + " has not been activated");
        } else {
            String activateRuleResponse = sonarClient.post("api/qualityprofiles/activate_rule",
                    ImmutableMap.of("profile_key", profileKey, "rule_key", "squid:" + instantiationKey, "severity",
                            "INFO", "params", ""));
            LOG.warn(activateRuleResponse);
        }
    }

}