/*
* SonarQube Java
* Copyright (C) 2013-2016 SonarSource SA
* mailto:contact 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.sonar.orchestrator.Orchestrator;
import com.sonar.orchestrator.build.Build;
import com.sonar.orchestrator.build.MavenBuild;
import com.sonar.orchestrator.build.SonarScanner;
import com.sonar.orchestrator.locator.FileLocation;
import no.finn.lambdacompanion.Try;
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.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class JavaRulingTest {
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.5")
.addPlugin("lits")
.build();
@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("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(".")
.setProperty("sonar.java.source", "1.5");
executeBuildWithCommonProperties(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 executeBuildWithCommonProperties(Build<?> build, String projectName) 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));
orchestrator.executeBuild(build);
assertNoDifferences(projectName);
}
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/rules/app");
Pattern pattern = Pattern.compile("java-rules-\\d+");
Matcher matcher = pattern.matcher(post);
if (matcher.find()) {
String profilekey = matcher.group();
sonarClient.post("api/qualityprofiles/activate_rule", ImmutableMap.<String, Object>of(
"profile_key", profilekey,
"rule_key", "squid:" + instantiationKey,
"severity", "INFO",
"params", ""));
} else {
LOG.error("Could not retrieve profile key : Template rule " + ruleTemplateKey + " has not been activated");
}
}
}