@@ -0,0 +1,32 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||
<modelVersion>4.0.0</modelVersion> | |||
<parent> | |||
<groupId>org.sonarsource.it</groupId> | |||
<artifactId>it-parent</artifactId> | |||
<version>5.2-SNAPSHOT</version> | |||
</parent> | |||
<artifactId>plugins</artifactId> | |||
<name>SonarQube Integration Tests :: Plugins</name> | |||
<description>The fake plugins used by integration tests</description> | |||
<packaging>pom</packaging> | |||
<build> | |||
<plugins> | |||
<plugin> | |||
<groupId>org.apache.maven.plugins</groupId> | |||
<artifactId>maven-surefire-plugin</artifactId> | |||
<configuration> | |||
<skip>true</skip> | |||
</configuration> | |||
</plugin> | |||
</plugins> | |||
</build> | |||
<modules> | |||
</modules> | |||
</project> |
@@ -0,0 +1,37 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||
<modelVersion>4.0.0</modelVersion> | |||
<parent> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar</artifactId> | |||
<version>5.2-SNAPSHOT</version> | |||
</parent> | |||
<groupId>org.sonarsource.it</groupId> | |||
<artifactId>it-parent</artifactId> | |||
<version>5.2-SNAPSHOT</version> | |||
<name>SonarQube Integration Tests</name> | |||
<packaging>pom</packaging> | |||
<modules> | |||
<module>plugins</module> | |||
<module>tests</module> | |||
</modules> | |||
<dependencies> | |||
<dependency> | |||
<groupId>org.codehaus.sonar.plugins</groupId> | |||
<artifactId>sonar-xoo-plugin</artifactId> | |||
<version>${pom.version}</version> | |||
<scope>provided</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar-application</artifactId> | |||
<version>${pom.version}</version> | |||
<scope>provided</scope> | |||
</dependency> | |||
</dependencies> | |||
</project> |
@@ -0,0 +1,5 @@ | |||
sonar.projectKey=sample | |||
sonar.projectName=Sample | |||
sonar.projectVersion=1.0-SNAPSHOT | |||
sonar.sources=src | |||
sonar.language=xoo |
@@ -0,0 +1,12 @@ | |||
package sample; | |||
public class Sample { | |||
public Sample(int i) { | |||
int j = i++; | |||
} | |||
private String myMethod() { | |||
return "hello"; | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
ncloc:13 | |||
complexity:7 | |||
coverage:89.0 | |||
test_execution_time:630 | |||
sqale_index:4830 | |||
#Used by dashboard/widgets tests | |||
complexity_in_classes:3 | |||
classes:1 | |||
comment_lines:3 | |||
public_api:5 | |||
public_undocumented_api:2 |
@@ -0,0 +1,12 @@ | |||
package sample; | |||
public class Sample { | |||
public Sample(int i) { | |||
int j = i++; | |||
} | |||
private String myMethod2() { | |||
return "hello"; | |||
} | |||
} |
@@ -0,0 +1,10 @@ | |||
ncloc:13 | |||
complexity:7 | |||
coverage:89.0 | |||
test_execution_time:630 | |||
sqale_index:4830 | |||
complexity_in_classes:3 | |||
classes:1 | |||
comment_lines:3 | |||
public_api:5 | |||
public_undocumented_api:2 |
@@ -0,0 +1,94 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||
<modelVersion>4.0.0</modelVersion> | |||
<parent> | |||
<groupId>org.sonarsource.it</groupId> | |||
<artifactId>it-parent</artifactId> | |||
<version>5.2-SNAPSHOT</version> | |||
</parent> | |||
<artifactId>tests</artifactId> | |||
<name>SonarQube Integration Tests :: Tests</name> | |||
<properties> | |||
<category>*</category> | |||
<surefire.argLine>-Xmx128m -server</surefire.argLine> | |||
</properties> | |||
<dependencies> | |||
<dependency> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar-ws-client</artifactId> | |||
<version>5.0</version> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.sonarsource.orchestrator</groupId> | |||
<artifactId>sonar-orchestrator</artifactId> | |||
<version>3.2</version> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>junit</groupId> | |||
<artifactId>junit</artifactId> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.assertj</groupId> | |||
<artifactId>assertj-core</artifactId> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.hamcrest</groupId> | |||
<artifactId>hamcrest-all</artifactId> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.googlecode.json-simple</groupId> | |||
<artifactId>json-simple</artifactId> | |||
<version>1.1</version> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.skyscreamer</groupId> | |||
<artifactId>jsonassert</artifactId> | |||
<version>1.2.0</version> | |||
<scope>test</scope> | |||
</dependency> | |||
<!-- Email notifications --> | |||
<dependency> | |||
<groupId>org.apache.commons</groupId> | |||
<artifactId>commons-email</artifactId> | |||
<version>1.2</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.subethamail</groupId> | |||
<artifactId>subethasmtp</artifactId> | |||
<version>3.1.6</version> | |||
</dependency> | |||
</dependencies> | |||
<build> | |||
<plugins> | |||
<plugin> | |||
<groupId>org.apache.maven.plugins</groupId> | |||
<artifactId>maven-surefire-plugin</artifactId> | |||
<configuration> | |||
<includes> | |||
<include>org/sonar/it/${category}/suite/*TestSuite.java</include> | |||
<!-- not included in suites --> | |||
<include>org/sonar/it/${category}/*Test.java</include> | |||
</includes> | |||
<excludes> | |||
<!-- included into suites --> | |||
<exclude>org/sonar/it/*/suite/*Test.java</exclude> | |||
</excludes> | |||
</configuration> | |||
</plugin> | |||
</plugins> | |||
</build> | |||
</project> |
@@ -0,0 +1,63 @@ | |||
/* | |||
* Copyright (C) 2009-2014 SonarSource SA | |||
* All rights reserved | |||
* mailto:contact AT sonarsource DOT com | |||
*/ | |||
package org.sonar.it; | |||
import com.google.common.base.Supplier; | |||
import com.google.common.base.Suppliers; | |||
import com.sonar.orchestrator.locator.FileLocation; | |||
import java.io.File; | |||
import org.apache.commons.io.FileUtils; | |||
public class ItUtils { | |||
private ItUtils() { | |||
} | |||
private static final Supplier<File> HOME_DIR = Suppliers.memoize(new Supplier<File>() { | |||
@Override | |||
public File get() { | |||
File dir = new File("it"); | |||
// intellij way | |||
if (dir.exists() && dir.isDirectory()) { | |||
return dir.getParentFile(); | |||
} | |||
// maven way | |||
dir = new File("../it"); | |||
if (dir.exists() && dir.isDirectory()) { | |||
return dir.getParentFile(); | |||
} | |||
throw new IllegalStateException("Fail to locate home directory"); | |||
} | |||
}); | |||
public static FileLocation xooPlugin() { | |||
File target = new File(HOME_DIR.get(), "plugins/sonar-xoo-plugin/target"); | |||
if (target.exists()) { | |||
for (File jar : FileUtils.listFiles(target, new String[] {"jar"}, false)) { | |||
if (jar.getName().startsWith("sonar-xoo-plugin-") && !jar.getName().contains("-source")) { | |||
return FileLocation.of(jar); | |||
} | |||
} | |||
} | |||
throw new IllegalStateException("XOO plugin is not built"); | |||
} | |||
/** | |||
* Locate the directory of sample project | |||
* | |||
* @param relativePath path related to the directory it/projects, for example "qualitygate/xoo-sample" | |||
*/ | |||
public static File projectDir(String relativePath) { | |||
File dir = new File(HOME_DIR.get(), "it/projects/" + relativePath); | |||
if (!dir.exists() || !dir.isDirectory()) { | |||
throw new IllegalStateException("Directory does not exist: " + dir.getAbsolutePath()); | |||
} | |||
return dir; | |||
} | |||
} |
@@ -0,0 +1,194 @@ | |||
/* | |||
* Copyright (C) 2009-2014 SonarSource SA | |||
* All rights reserved | |||
* mailto:contact AT sonarsource DOT com | |||
*/ | |||
package org.sonar.it.qualitygate; | |||
import com.sonar.orchestrator.Orchestrator; | |||
import com.sonar.orchestrator.build.SonarRunner; | |||
import org.junit.Before; | |||
import org.junit.ClassRule; | |||
import org.junit.Test; | |||
import org.sonar.it.ItUtils; | |||
import org.sonar.wsclient.project.NewProject; | |||
import org.sonar.wsclient.qualitygate.NewCondition; | |||
import org.sonar.wsclient.qualitygate.QualityGate; | |||
import org.sonar.wsclient.qualitygate.QualityGateClient; | |||
import org.sonar.wsclient.services.Measure; | |||
import org.sonar.wsclient.services.Resource; | |||
import org.sonar.wsclient.services.ResourceQuery; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.sonar.it.ItUtils.projectDir; | |||
public class QualityGateTest { | |||
private static final String PROJECT_KEY = "sample"; | |||
private long provisionnedProjectId = -1L; | |||
@ClassRule | |||
public static Orchestrator orchestrator = Orchestrator.builderEnv() | |||
.addPlugin(ItUtils.xooPlugin()) | |||
.build(); | |||
@Before | |||
public void cleanUp() throws Exception { | |||
orchestrator.resetData(); | |||
provisionnedProjectId = Long.parseLong(orchestrator.getServer().adminWsClient().projectClient().create(NewProject.create().key(PROJECT_KEY).name("Sample")).id()); | |||
} | |||
@Test | |||
public void do_not_compute_status_if_no_gate() { | |||
SonarRunner build = SonarRunner.create(projectDir("qualitygate/xoo-sample")); | |||
orchestrator.executeBuild(build); | |||
assertThat(fetchResourceWithGateStatus()).isNull(); | |||
} | |||
@Test | |||
public void status_ok_if_empty_gate() { | |||
QualityGate empty = qgClient().create("Empty"); | |||
qgClient().setDefault(empty.id()); | |||
try { | |||
SonarRunner build = SonarRunner.create(projectDir("qualitygate/xoo-sample")); | |||
orchestrator.executeBuild(build); | |||
assertThat(fetchGateStatus().getData()).isEqualTo("OK"); | |||
} finally { | |||
qgClient().unsetDefault(); | |||
qgClient().destroy(empty.id()); | |||
} | |||
} | |||
@Test | |||
public void test_status_ok() { | |||
QualityGate simple = qgClient().create("SimpleWithHighThreshold"); | |||
qgClient().setDefault(simple.id()); | |||
qgClient().createCondition(NewCondition.create(simple.id()).metricKey("ncloc").operator("GT").warningThreshold("40")); | |||
try { | |||
SonarRunner build = SonarRunner.create(projectDir("qualitygate/xoo-sample")); | |||
orchestrator.executeBuild(build); | |||
assertThat(fetchGateStatus().getData()).isEqualTo("OK"); | |||
} finally { | |||
qgClient().unsetDefault(); | |||
qgClient().destroy(simple.id()); | |||
} | |||
} | |||
@Test | |||
public void test_status_warning() { | |||
QualityGate simple = qgClient().create("SimpleWithLowThreshold"); | |||
qgClient().setDefault(simple.id()); | |||
qgClient().createCondition(NewCondition.create(simple.id()).metricKey("ncloc").operator("GT").warningThreshold("10")); | |||
try { | |||
SonarRunner build = SonarRunner.create(projectDir("qualitygate/xoo-sample")); | |||
orchestrator.executeBuild(build); | |||
assertThat(fetchGateStatus().getData()).isEqualTo("WARN"); | |||
} finally { | |||
qgClient().unsetDefault(); | |||
qgClient().destroy(simple.id()); | |||
} | |||
} | |||
@Test | |||
public void test_status_error() { | |||
QualityGate simple = qgClient().create("SimpleWithLowThreshold"); | |||
qgClient().setDefault(simple.id()); | |||
qgClient().createCondition(NewCondition.create(simple.id()).metricKey("ncloc").operator("GT").errorThreshold("10")); | |||
try { | |||
SonarRunner build = SonarRunner.create(projectDir("qualitygate/xoo-sample")); | |||
orchestrator.executeBuild(build); | |||
assertThat(fetchGateStatus().getData()).isEqualTo("ERROR"); | |||
} finally { | |||
qgClient().unsetDefault(); | |||
qgClient().destroy(simple.id()); | |||
} | |||
} | |||
@Test | |||
public void use_server_settings_instead_of_default_gate() { | |||
QualityGate alert = qgClient().create("AlertWithLowThreshold"); | |||
qgClient().createCondition(NewCondition.create(alert.id()).metricKey("ncloc").operator("GT").warningThreshold("10")); | |||
QualityGate error = qgClient().create("ErrorWithLowThreshold"); | |||
qgClient().createCondition(NewCondition.create(error.id()).metricKey("ncloc").operator("GT").errorThreshold("10")); | |||
qgClient().setDefault(alert.id()); | |||
qgClient().selectProject(error.id(), provisionnedProjectId); | |||
try { | |||
SonarRunner build = SonarRunner.create(projectDir("qualitygate/xoo-sample")); | |||
orchestrator.executeBuild(build); | |||
assertThat(fetchGateStatus().getData()).isEqualTo("ERROR"); | |||
} finally { | |||
qgClient().unsetDefault(); | |||
qgClient().destroy(alert.id()); | |||
qgClient().destroy(error.id()); | |||
} | |||
} | |||
@Test | |||
public void conditions_on_multiple_metric_types() { | |||
QualityGate allTypes = qgClient().create("AllMetricTypes"); | |||
qgClient().createCondition(NewCondition.create(allTypes.id()).metricKey("ncloc").operator("GT").warningThreshold("10")); | |||
qgClient().createCondition(NewCondition.create(allTypes.id()).metricKey("duplicated_lines_density").operator("GT").warningThreshold("20")); | |||
qgClient().setDefault(allTypes.id()); | |||
try { | |||
SonarRunner build = SonarRunner.create(projectDir("qualitygate/xoo-sample")) | |||
.setProperty("sonar.cpd.xoo.minimumLines", "2") | |||
.setProperty("sonar.cpd.xoo.minimumTokens", "5"); | |||
orchestrator.executeBuild(build); | |||
Measure alertStatus = fetchGateStatus(); | |||
assertThat(alertStatus.getData()).isEqualTo("WARN"); | |||
assertThat(alertStatus.getAlertText()) | |||
.contains("Lines of code > 10") | |||
.contains("Duplicated lines (%) > 20"); | |||
} finally { | |||
qgClient().unsetDefault(); | |||
qgClient().destroy(allTypes.id()); | |||
} | |||
} | |||
@Test | |||
public void compute_gate_status_on_metric_variation() { | |||
QualityGate simple = qgClient().create("SimpleWithDifferential"); | |||
qgClient().setDefault(simple.id()); | |||
qgClient().createCondition(NewCondition.create(simple.id()).metricKey("ncloc").period(1).operator("EQ").warningThreshold("0")); | |||
try { | |||
SonarRunner build = SonarRunner.create(projectDir("qualitygate/xoo-sample")); | |||
orchestrator.executeBuild(build); | |||
assertThat(fetchGateStatus().getData()).isEqualTo("OK"); | |||
orchestrator.executeBuild(build); | |||
assertThat(fetchGateStatus().getData()).isEqualTo("WARN"); | |||
} finally { | |||
qgClient().unsetDefault(); | |||
qgClient().destroy(simple.id()); | |||
} | |||
} | |||
private Measure fetchGateStatus() { | |||
return fetchResourceWithGateStatus().getMeasure("alert_status"); | |||
} | |||
private Resource fetchResourceWithGateStatus() { | |||
return orchestrator.getServer().getWsClient().find(ResourceQuery.createForMetrics(PROJECT_KEY, "alert_status").setIncludeAlerts(true)); | |||
} | |||
private static QualityGateClient qgClient() { | |||
return orchestrator.getServer().adminWsClient().qualityGateClient(); | |||
} | |||
} |
@@ -0,0 +1,71 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.xoo.rule; | |||
import org.sonar.api.batch.Sensor; | |||
import org.sonar.api.batch.SensorContext; | |||
import org.sonar.api.batch.fs.FileSystem; | |||
import org.sonar.api.batch.fs.InputFile; | |||
import org.sonar.api.batch.rule.ActiveRules; | |||
import org.sonar.api.resources.File; | |||
import org.sonar.api.resources.Project; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.xoo.Xoo; | |||
/** | |||
* @deprecated to be replaced by {@link org.sonar.api.batch.sensor.Sensor} | |||
*/ | |||
public abstract class AbstractDeprecatedXooRuleSensor implements Sensor { | |||
private final FileSystem fs; | |||
private final ActiveRules activeRules; | |||
public AbstractDeprecatedXooRuleSensor(FileSystem fs, ActiveRules activeRules) { | |||
this.fs = fs; | |||
this.activeRules = activeRules; | |||
} | |||
protected abstract String getRuleKey(); | |||
@Override | |||
public boolean shouldExecuteOnProject(Project project) { | |||
return fs.hasFiles(fs.predicates().hasLanguages(Xoo.KEY)) | |||
&& (activeRules.find(RuleKey.of(XooRulesDefinition.XOO_REPOSITORY, getRuleKey())) != null); | |||
} | |||
@Override | |||
public final void analyse(Project project, SensorContext context) { | |||
doAnalyse(context, Xoo.KEY); | |||
} | |||
private void doAnalyse(SensorContext context, String languageKey) { | |||
RuleKey ruleKey = RuleKey.of(XooRulesDefinition.XOO_REPOSITORY, getRuleKey()); | |||
if (activeRules.find(ruleKey) == null) { | |||
return; | |||
} | |||
for (InputFile inputFile : fs.inputFiles(fs.predicates().hasLanguage(languageKey))) { | |||
File sonarFile = File.create(inputFile.relativePath()); | |||
sonarFile = context.getResource(sonarFile); | |||
processFile(inputFile, sonarFile, context, ruleKey, languageKey); | |||
} | |||
} | |||
protected abstract void processFile(InputFile inputFile, File sonarFile, SensorContext context, RuleKey ruleKey, String languageKey); | |||
} |
@@ -0,0 +1,59 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.xoo.rule; | |||
import org.sonar.api.batch.SensorContext; | |||
import org.sonar.api.batch.fs.FileSystem; | |||
import org.sonar.api.batch.fs.InputFile; | |||
import org.sonar.api.batch.rule.ActiveRules; | |||
import org.sonar.api.component.ResourcePerspectives; | |||
import org.sonar.api.config.Settings; | |||
import org.sonar.api.issue.Issuable; | |||
import org.sonar.api.rule.RuleKey; | |||
public class CustomMessageSensor extends AbstractDeprecatedXooRuleSensor { | |||
public static final String RULE_KEY = "CustomMessage"; | |||
private static final String MESSAGE_PROPERTY = "sonar.customMessage.message"; | |||
private final ResourcePerspectives perspectives; | |||
private final Settings settings; | |||
public CustomMessageSensor(ResourcePerspectives perspectives, Settings settings, FileSystem fs, ActiveRules activeRules) { | |||
super(fs, activeRules); | |||
this.perspectives = perspectives; | |||
this.settings = settings; | |||
} | |||
@Override | |||
protected String getRuleKey() { | |||
return RULE_KEY; | |||
} | |||
@Override | |||
protected void processFile(InputFile inputFile, org.sonar.api.resources.File sonarFile, SensorContext context, RuleKey ruleKey, String languageKey) { | |||
Issuable issuable = perspectives.as(Issuable.class, sonarFile); | |||
issuable.addIssue(issuable.newIssueBuilder() | |||
.ruleKey(ruleKey) | |||
.message(settings.getString(MESSAGE_PROPERTY)) | |||
.build()); | |||
} | |||
} |
@@ -0,0 +1,85 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.xoo.rule; | |||
import java.io.IOException; | |||
import java.util.List; | |||
import org.apache.commons.io.FileUtils; | |||
import org.sonar.api.batch.SensorContext; | |||
import org.sonar.api.batch.fs.FileSystem; | |||
import org.sonar.api.batch.fs.InputFile; | |||
import org.sonar.api.batch.rule.ActiveRules; | |||
import org.sonar.api.component.ResourcePerspectives; | |||
import org.sonar.api.config.Settings; | |||
import org.sonar.api.issue.Issuable; | |||
import org.sonar.api.rule.RuleKey; | |||
/** | |||
* Generate issues on all the occurrences of a given tag in xoo sources. | |||
*/ | |||
public class HasTagSensor extends AbstractDeprecatedXooRuleSensor { | |||
public static final String RULE_KEY = "HasTag"; | |||
private static final String EFFORT_TO_FIX_PROPERTY = "sonar.hasTag.effortToFix"; | |||
private final ResourcePerspectives perspectives; | |||
private final Settings settings; | |||
private final ActiveRules activeRules; | |||
private FileSystem fs; | |||
public HasTagSensor(FileSystem fs, ResourcePerspectives perspectives, Settings settings, ActiveRules activeRules) { | |||
super(fs, activeRules); | |||
this.fs = fs; | |||
this.perspectives = perspectives; | |||
this.settings = settings; | |||
this.activeRules = activeRules; | |||
} | |||
@Override | |||
protected String getRuleKey() { | |||
return RULE_KEY; | |||
} | |||
@Override | |||
protected void processFile(InputFile inputFile, org.sonar.api.resources.File sonarFile, SensorContext context, RuleKey ruleKey, String languageKey) { | |||
org.sonar.api.batch.rule.ActiveRule activeRule = activeRules.find(ruleKey); | |||
String tag = activeRule.param("tag"); | |||
if (tag == null) { | |||
throw new IllegalStateException("Rule is badly configured. The parameter 'tag' is missing."); | |||
} | |||
try { | |||
Issuable issuable = perspectives.as(Issuable.class, sonarFile); | |||
List<String> lines = FileUtils.readLines(inputFile.file(), fs.encoding().name()); | |||
for (int index = 0; index < lines.size(); index++) { | |||
if (lines.get(index).contains(tag)) { | |||
issuable.addIssue(issuable.newIssueBuilder() | |||
.effortToFix(settings.getDouble(EFFORT_TO_FIX_PROPERTY)) | |||
.line(index + 1) | |||
.ruleKey(ruleKey) | |||
.build()); | |||
} | |||
} | |||
} catch (IOException e) { | |||
throw new IllegalStateException("Fail to process " + inputFile, e); | |||
} | |||
} | |||
} |
@@ -0,0 +1,57 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.xoo.rule; | |||
import org.sonar.api.batch.SensorContext; | |||
import org.sonar.api.batch.fs.FileSystem; | |||
import org.sonar.api.batch.fs.InputFile; | |||
import org.sonar.api.batch.rule.ActiveRules; | |||
import org.sonar.api.component.ResourcePerspectives; | |||
import org.sonar.api.issue.Issuable; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.rule.Severity; | |||
public class OneBlockerIssuePerFileSensor extends AbstractDeprecatedXooRuleSensor { | |||
public static final String RULE_KEY = "OneBlockerIssuePerFile"; | |||
private final ResourcePerspectives perspectives; | |||
public OneBlockerIssuePerFileSensor(ResourcePerspectives perspectives, FileSystem fs, ActiveRules activeRules) { | |||
super(fs, activeRules); | |||
this.perspectives = perspectives; | |||
} | |||
@Override | |||
protected String getRuleKey() { | |||
return RULE_KEY; | |||
} | |||
@Override | |||
protected void processFile(InputFile inputFile, org.sonar.api.resources.File sonarFile, SensorContext context, RuleKey ruleKey, String languageKey) { | |||
Issuable issuable = perspectives.as(Issuable.class, sonarFile); | |||
issuable.addIssue(issuable.newIssueBuilder() | |||
.ruleKey(ruleKey) | |||
.severity(Severity.BLOCKER) | |||
.message("This issue is generated on each file. Severity is blocker, whatever quality profile") | |||
.build()); | |||
} | |||
} |
@@ -0,0 +1,60 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.xoo.rule; | |||
import org.sonar.api.batch.SensorContext; | |||
import org.sonar.api.batch.fs.FileSystem; | |||
import org.sonar.api.batch.fs.InputFile; | |||
import org.sonar.api.batch.rule.ActiveRules; | |||
import org.sonar.api.component.ResourcePerspectives; | |||
import org.sonar.api.config.Settings; | |||
import org.sonar.api.issue.Issuable; | |||
import org.sonar.api.rule.RuleKey; | |||
public class OneIssuePerFileSensor extends AbstractDeprecatedXooRuleSensor { | |||
public static final String RULE_KEY = "OneIssuePerFile"; | |||
private static final String EFFORT_TO_FIX_PROPERTY = "sonar.oneIssuePerFile.effortToFix"; | |||
private final ResourcePerspectives perspectives; | |||
private final Settings settings; | |||
public OneIssuePerFileSensor(ResourcePerspectives perspectives, Settings settings, FileSystem fs, ActiveRules activeRules) { | |||
super(fs, activeRules); | |||
this.perspectives = perspectives; | |||
this.settings = settings; | |||
} | |||
@Override | |||
protected String getRuleKey() { | |||
return RULE_KEY; | |||
} | |||
@Override | |||
protected void processFile(InputFile inputFile, org.sonar.api.resources.File sonarFile, SensorContext context, RuleKey ruleKey, String languageKey) { | |||
Issuable issuable = perspectives.as(Issuable.class, sonarFile); | |||
issuable.addIssue(issuable.newIssueBuilder() | |||
.ruleKey(ruleKey) | |||
.effortToFix(settings.getDouble(EFFORT_TO_FIX_PROPERTY)) | |||
.message("This issue is generated on each file") | |||
.build()); | |||
} | |||
} |
@@ -0,0 +1,59 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.xoo.rule; | |||
import org.sonar.api.batch.Sensor; | |||
import org.sonar.api.batch.SensorContext; | |||
import org.sonar.api.batch.fs.FileSystem; | |||
import org.sonar.api.batch.rule.ActiveRules; | |||
import org.sonar.api.component.ResourcePerspectives; | |||
import org.sonar.api.issue.Issuable; | |||
import org.sonar.api.resources.Project; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.xoo.Xoo; | |||
public class OneIssuePerModuleSensor implements Sensor { | |||
public static final String RULE_KEY = "OneIssuePerModule"; | |||
private final ResourcePerspectives perspectives; | |||
private final FileSystem fs; | |||
private final ActiveRules activeRules; | |||
public OneIssuePerModuleSensor(ResourcePerspectives perspectives, FileSystem fs, ActiveRules activeRules) { | |||
this.perspectives = perspectives; | |||
this.fs = fs; | |||
this.activeRules = activeRules; | |||
} | |||
@Override | |||
public void analyse(Project project, SensorContext context) { | |||
Issuable issuable = perspectives.as(Issuable.class, project); | |||
issuable.addIssue(issuable.newIssueBuilder() | |||
.ruleKey(RuleKey.of(XooRulesDefinition.XOO_REPOSITORY, RULE_KEY)) | |||
.message("This issue is generated on each module") | |||
.build()); | |||
} | |||
@Override | |||
public boolean shouldExecuteOnProject(Project project) { | |||
return fs.hasFiles(fs.predicates().hasLanguages(Xoo.KEY)) && (activeRules.find(RuleKey.of(XooRulesDefinition.XOO_REPOSITORY, RULE_KEY)) != null); | |||
} | |||
} |
@@ -32,7 +32,7 @@ public class XooQualityProfile extends ProfileDefinition { | |||
public RulesProfile createProfile(ValidationMessages validation) { | |||
final RulesProfile profile = RulesProfile.create("Basic", Xoo.KEY); | |||
profile.activateRule(Rule.create(XooRulesDefinition.XOO_REPOSITORY, "x1"), RulePriority.MAJOR); | |||
profile.activateRule(Rule.create(XooRulesDefinition.XOO_REPOSITORY, HasTagSensor.RULE_KEY), RulePriority.MAJOR); | |||
return profile; | |||
} |
@@ -19,13 +19,9 @@ | |||
*/ | |||
package org.sonar.xoo.rule; | |||
import org.sonar.api.rule.RuleStatus; | |||
import org.sonar.api.rule.Severity; | |||
import org.sonar.api.server.rule.RuleParamType; | |||
import org.sonar.api.server.rule.RulesDefinition; | |||
import org.sonar.api.server.rule.RulesDefinitionAnnotationLoader; | |||
import org.sonar.xoo.Xoo; | |||
import org.sonar.xoo.checks.Check; | |||
/** | |||
* Define all the coding rules that are supported on the repository named "xoo". | |||
@@ -36,41 +32,50 @@ public class XooRulesDefinition implements RulesDefinition { | |||
@Override | |||
public void define(Context context) { | |||
NewRepository repository = context.createRepository(XOO_REPOSITORY, Xoo.KEY).setName("Xoo"); | |||
NewRepository repo = context.createRepository(XOO_REPOSITORY, Xoo.KEY).setName("Xoo"); | |||
// Load checks | |||
new RulesDefinitionAnnotationLoader().load(repository, Check.ALL); | |||
NewRule hasTag = repo.createRule(HasTagSensor.RULE_KEY).setName("Has Tag") | |||
.setHtmlDescription("Search for a given tag in Xoo files"); | |||
hasTag.setDebtSubCharacteristic(RulesDefinition.SubCharacteristics.READABILITY) | |||
.setDebtRemediationFunction(hasTag.debtRemediationFunctions().constantPerIssue("2min")); | |||
hasTag.createParam("tag") | |||
.setDefaultValue("xoo") | |||
.setDescription("The tag to search for"); | |||
// define a single rule programmatically. Note that rules | |||
// can be loaded from JSON or XML files too. | |||
NewRule x1Rule = repository.createRule("x1") | |||
.setName("No empty line") | |||
.setMarkdownDescription("Generate an issue on *empty* lines of Xoo source files") | |||
NewRule ruleWithParameters = repo.createRule("RuleWithParameters").setName("Rule with parameters") | |||
.setHtmlDescription("Rule containing parameter of different types : boolean, integer, etc. For information, no issue will be linked to this rule."); | |||
ruleWithParameters.createParam("string").setType(RuleParamType.STRING); | |||
ruleWithParameters.createParam("text").setType(RuleParamType.TEXT); | |||
ruleWithParameters.createParam("boolean").setType(RuleParamType.BOOLEAN); | |||
ruleWithParameters.createParam("integer").setType(RuleParamType.INTEGER); | |||
ruleWithParameters.createParam("float").setType(RuleParamType.FLOAT); | |||
// optional tags | |||
.setTags("style", "security") | |||
NewRule oneIssuePerLine = repo.createRule(OneIssuePerLineSensor.RULE_KEY).setName("One Issue Per Line") | |||
.setHtmlDescription("Generate an issue on each line of a file. It requires the metric \"lines\"."); | |||
oneIssuePerLine.setDebtSubCharacteristic(RulesDefinition.SubCharacteristics.MEMORY_EFFICIENCY) | |||
.setDebtRemediationFunction(hasTag.debtRemediationFunctions().linear("1min")) | |||
.setEffortToFixDescription("It takes about 1 minute to an experienced software craftsman to remove a line of code"); | |||
// optional status. Default value is READY. | |||
.setStatus(RuleStatus.BETA) | |||
NewRule oneIssuePerFile = repo.createRule(OneIssuePerFileSensor.RULE_KEY).setName("One Issue Per File") | |||
.setHtmlDescription("Generate an issue on each file"); | |||
oneIssuePerFile.setDebtSubCharacteristic(RulesDefinition.SubCharacteristics.ARCHITECTURE_CHANGEABILITY) | |||
.setDebtRemediationFunction(hasTag.debtRemediationFunctions().linear("10min")); | |||
// default severity when the rule is activated on a Quality profile. Default value is MAJOR. | |||
.setSeverity(Severity.MINOR); | |||
NewRule oneIssuePerModule = repo.createRule(OneIssuePerModuleSensor.RULE_KEY).setName("One Issue Per Module") | |||
.setHtmlDescription("Generate an issue on each module"); | |||
oneIssuePerModule.setDebtSubCharacteristic(RulesDefinition.SubCharacteristics.API_ABUSE) | |||
.setDebtRemediationFunction(hasTag.debtRemediationFunctions().linearWithOffset("25min", "1h")) | |||
.setEffortToFixDescription("A certified architect will need roughly half an hour to start working on removal of modules, " + | |||
"then it's about one hour per module."); | |||
// debt-related information | |||
x1Rule | |||
.setDebtSubCharacteristic(SubCharacteristics.INTEGRATION_TESTABILITY) | |||
.setDebtRemediationFunction(x1Rule.debtRemediationFunctions().linearWithOffset("1h", "30min")) | |||
.setEffortToFixDescription("Effort to fix issue on one line"); | |||
repo.createRule(OneBlockerIssuePerFileSensor.RULE_KEY).setName("One Blocker Issue Per File") | |||
.setHtmlDescription("Generate a blocker issue on each file, whatever the severity declared in the Quality profile"); | |||
x1Rule.createParam("acceptWhitespace") | |||
.setDefaultValue("false") | |||
.setType(RuleParamType.BOOLEAN) | |||
.setDescription("= Accept whitespace (``\\s|\\t``) on the line\nThis property is available so that a line containing only whitespace is not considered empty.\n" | |||
+ "== Example with property set to ``false``\n``xoo\n <- One issue here\n<- And one here\n``\n\n" | |||
+ "== Example with property set to ``true``\n``xoo\n <- No issue here\n<- But one here\n``\n"); | |||
repo.createRule(CustomMessageSensor.RULE_KEY).setName("Issue With Custom Message") | |||
.setHtmlDescription("Generate an issue on each file with a custom message"); | |||
repo.done(); | |||
// don't forget to call done() to finalize the definition | |||
repository.done(); | |||
} | |||
} |
@@ -37,17 +37,14 @@ public class XooRulesDefinitionTest { | |||
assertThat(repo).isNotNull(); | |||
assertThat(repo.name()).isEqualTo("Xoo"); | |||
assertThat(repo.language()).isEqualTo("xoo"); | |||
assertThat(repo.rules()).hasSize(2); | |||
assertThat(repo.rules()).hasSize(7); | |||
RulesDefinition.Rule x1 = repo.rule("x1"); | |||
assertThat(x1.key()).isEqualTo("x1"); | |||
assertThat(x1.tags()).containsOnly("style", "security"); | |||
assertThat(x1.markdownDescription()).isNotEmpty(); | |||
assertThat(x1.debtSubCharacteristic()).isEqualTo(RulesDefinition.SubCharacteristics.INTEGRATION_TESTABILITY); | |||
assertThat(x1.debtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET); | |||
assertThat(x1.debtRemediationFunction().coefficient()).isEqualTo("1h"); | |||
assertThat(x1.debtRemediationFunction().offset()).isEqualTo("30min"); | |||
assertThat(x1.effortToFixDescription()).isEqualTo("Effort to fix issue on one line"); | |||
RulesDefinition.Rule rule = repo.rule(OneIssuePerLineSensor.RULE_KEY); | |||
assertThat(rule.name()).isNotEmpty(); | |||
assertThat(rule.debtSubCharacteristic()).isEqualTo(RulesDefinition.SubCharacteristics.MEMORY_EFFICIENCY); | |||
assertThat(rule.debtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.LINEAR); | |||
assertThat(rule.debtRemediationFunction().coefficient()).isEqualTo("1min"); | |||
assertThat(rule.debtRemediationFunction().offset()).isNull(); | |||
assertThat(rule.effortToFixDescription()).isNotEmpty(); | |||
} | |||
} |
@@ -1,5 +1,6 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | |||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | |||
<modelVersion>4.0.0</modelVersion> | |||
<groupId>org.codehaus.sonar</groupId> | |||
@@ -1134,7 +1135,6 @@ | |||
</dependency> | |||
<!-- tomcat --> | |||
<dependency> | |||
<groupId>org.apache.tomcat.embed</groupId> | |||
@@ -1613,6 +1613,14 @@ | |||
<coveragePerTest>true</coveragePerTest> | |||
</properties> | |||
</profile> | |||
<profile> | |||
<!-- integration tests --> | |||
<id>it</id> | |||
<modules> | |||
<module>it</module> | |||
</modules> | |||
</profile> | |||
</profiles> | |||
</project> |