Browse Source

SONAR-4908 refactor RulesDefinition API (debt functions, XML)

tags/4.3
Simon Brandhof 10 years ago
parent
commit
e2b033fccc
23 changed files with 435 additions and 150 deletions
  1. 1
    1
      plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/XooRulesDefinitionTest.java
  2. 0
    0
      sonar-plugin-api/src/main/java/org/sonar/api/i18n/RuleI18n.java
  3. 22
    7
      sonar-plugin-api/src/main/java/org/sonar/api/server/debt/DebtRemediationFunction.java
  4. 46
    28
      sonar-plugin-api/src/main/java/org/sonar/api/server/debt/internal/DefaultDebtRemediationFunction.java
  5. 0
    35
      sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DebtRemediationFunctions.java
  6. 10
    3
      sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DefaultDebtRemediationFunctions.java
  7. 1
    1
      sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RuleTagFormat.java
  8. 101
    5
      sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java
  9. 70
    0
      sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinitionI18nLoader.java
  10. 2
    2
      sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java
  11. 42
    28
      sonar-plugin-api/src/test/java/org/sonar/api/server/debt/DefaultDebtRemediationFunctionTest.java
  12. 1
    1
      sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RuleTagFormatTest.java
  13. 99
    0
      sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionI18nLoaderTest.java
  14. 31
    30
      sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionTest.java
  15. 0
    0
      sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RulesDefinitionTest/sample.html
  16. 1
    1
      sonar-server/src/main/java/org/sonar/server/debt/DebtModelBackup.java
  17. 1
    1
      sonar-server/src/main/java/org/sonar/server/debt/DebtModelXMLExporter.java
  18. 1
    1
      sonar-server/src/main/java/org/sonar/server/debt/DebtRulesXMLImporter.java
  19. 1
    1
      sonar-server/src/main/java/org/sonar/server/rule/RuleRegistration.java
  20. 1
    1
      sonar-server/src/test/java/org/sonar/server/debt/DebtModelBackupTest.java
  21. 1
    1
      sonar-server/src/test/java/org/sonar/server/debt/DebtModelXMLExporterTest.java
  22. 2
    2
      sonar-server/src/test/java/org/sonar/server/debt/DebtRulesXMLImporterTest.java
  23. 1
    1
      sonar-server/src/test/java/org/sonar/server/rule/DeprecatedRulesDefinitionTest.java

+ 1
- 1
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/XooRulesDefinitionTest.java View File

@@ -20,7 +20,7 @@
package org.sonar.xoo.rule;

import org.junit.Test;
import org.sonar.api.server.rule.DebtRemediationFunction;
import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.server.rule.RulesDefinition;

import static org.fest.assertions.Assertions.assertThat;

sonar-deprecated/src/main/java/org/sonar/api/i18n/RuleI18n.java → sonar-plugin-api/src/main/java/org/sonar/api/i18n/RuleI18n.java View File


sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DebtRemediationFunction.java → sonar-plugin-api/src/main/java/org/sonar/api/server/debt/DebtRemediationFunction.java View File

@@ -18,30 +18,45 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.api.server.rule;
package org.sonar.api.server.debt;

import javax.annotation.CheckForNull;

/**
* Function used to calculate the remediation cost of an issue. There are three types :
* <ul>
* <li>
* <b>Linear</b> - Each issue of the rule costs the same amount of time (factor) to fix.
* </li>
* <li>
* <b>Linear with offset</b> - It takes a certain amount of time to analyze the issues of such kind on the file (offset).
* Then, each issue of the rule costs the same amount of time (factor) to fix. Total remediation cost
* by file = offset + (number of issues x factor)
* </li>
* <li><b>Constant/issue</b> - The cost to fix all the issues of the rule is the same whatever the number of issues
* of this rule in the file. Total remediation cost by file = constant
* </li>
* </ul>
*
* @since 4.3
*/
public interface DebtRemediationFunction {

static class ValidationException extends RuntimeException {
public ValidationException(String message) {
super(message);
}
}

static enum Type {
LINEAR, LINEAR_OFFSET, CONSTANT_ISSUE
}

Type type();

/**
* Factor is set on types {@link Type#LINEAR} and {@link Type#LINEAR_OFFSET}, else it's null.
*/
@CheckForNull
String factor();

/**
* Offset is set on types {@link Type#LINEAR_OFFSET} and {@link Type#CONSTANT_ISSUE}, else it's null.
*/
@CheckForNull
String offset();


sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DefaultDebtRemediationFunction.java → sonar-plugin-api/src/main/java/org/sonar/api/server/debt/internal/DefaultDebtRemediationFunction.java View File

@@ -18,31 +18,45 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.api.server.rule;
package org.sonar.api.server.debt.internal;

import com.google.common.base.Objects;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.utils.Duration;

import javax.annotation.CheckForNull;
import javax.annotation.Nullable;

class DefaultDebtRemediationFunction implements DebtRemediationFunction {
public class DefaultDebtRemediationFunction implements DebtRemediationFunction {

private Type type;
private String factor;
private String offset;
private static final int HOURS_IN_DAY = 24;

private final Type type;
private final String factor;
private final String offset;

public DefaultDebtRemediationFunction(Type type, @Nullable String factor, @Nullable String offset) {
this.type = type;
// TODO validate factor and offset format
this.factor = StringUtils.deleteWhitespace(factor);
this.offset = StringUtils.deleteWhitespace(offset);
this.factor = sanitizeValue("factor", factor);
this.offset = sanitizeValue("offset", offset);
validate();
}

@CheckForNull
private String sanitizeValue(String label, @Nullable String s) {
if (StringUtils.isNotBlank(s)) {
try {
Duration duration = Duration.decode(s, HOURS_IN_DAY);
return duration.encode(HOURS_IN_DAY);
} catch (Exception e) {
throw new IllegalArgumentException(String.format("Invalid %s: %s", label, s), e);
}
}
return null;
}

@Override
public Type type() {
return type;
@@ -64,21 +78,21 @@ class DefaultDebtRemediationFunction implements DebtRemediationFunction {
switch (type) {
case LINEAR:
if (this.factor == null || this.offset != null) {
throw new ValidationException(String.format("%s is invalid, Linear remediation function should only define a factor", this));
throw new IllegalArgumentException(String.format("Only factor must be set on %s", this));
}
break;
case LINEAR_OFFSET:
if (this.factor == null || this.offset == null) {
throw new ValidationException(String.format("%s is invalid, Linear with offset remediation function should define both factor and offset", this));
throw new IllegalArgumentException(String.format("Both factor and offset are required on %s", this));
}
break;
case CONSTANT_ISSUE:
if (this.factor != null || this.offset == null) {
throw new ValidationException(String.format("%s is invalid, Constant/issue remediation function should only define an offset", this));
throw new IllegalArgumentException(String.format("Only offset must be set on %s", this));
}
break;
default:
throw new IllegalStateException(String.format("Remediation function of %s is unknown", this));
throw new IllegalArgumentException(String.format("Unknown type on %s", this));
}
}

@@ -90,26 +104,30 @@ class DefaultDebtRemediationFunction implements DebtRemediationFunction {
if (o == null || getClass() != o.getClass()) {
return false;
}

DebtRemediationFunction that = (DebtRemediationFunction) o;
return new EqualsBuilder()
.append(type, that.type())
.append(factor, that.factor())
.append(offset, that.offset())
.isEquals();
DefaultDebtRemediationFunction that = (DefaultDebtRemediationFunction) o;
if (factor != null ? !factor.equals(that.factor) : that.factor != null) {
return false;
}
if (offset != null ? !offset.equals(that.offset) : that.offset != null) {
return false;
}
return type == that.type;
}

@Override
public int hashCode() {
return new HashCodeBuilder(15, 33)
.append(type)
.append(factor)
.append(offset)
.toHashCode();
int result = type.hashCode();
result = 31 * result + (factor != null ? factor.hashCode() : 0);
result = 31 * result + (offset != null ? offset.hashCode() : 0);
return result;
}

@Override
public String toString() {
return new ReflectionToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).toString();
return Objects.toStringHelper(DebtRemediationFunction.class)
.add("type", type)
.add("factor", factor)
.add("offset", offset)
.toString();
}
}

+ 0
- 35
sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DebtRemediationFunctions.java View File

@@ -1,35 +0,0 @@
/*
* 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.api.server.rule;

/**
* Factory of {@link org.sonar.api.server.rule.DebtRemediationFunction}
*
* @since 4.3
*/
public interface DebtRemediationFunctions {

DebtRemediationFunction linear(String factor);

DebtRemediationFunction linearWithOffset(String factor, String offset);

DebtRemediationFunction constantPerIssue(String offset);
}

+ 10
- 3
sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DefaultDebtRemediationFunctions.java View File

@@ -17,14 +17,21 @@
* 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.api.server.rule;

import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.server.debt.internal.DefaultDebtRemediationFunction;
import org.sonar.api.utils.MessageException;

import javax.annotation.Nullable;

class DefaultDebtRemediationFunctions implements DebtRemediationFunctions {
/**
* Factory of {@link org.sonar.api.server.debt.DebtRemediationFunction} that keeps
* a context of rule for better error messages. Used only when declaring rules.
*
* @see org.sonar.api.server.rule.RulesDefinition
*/
class DefaultDebtRemediationFunctions implements RulesDefinition.DebtRemediationFunctions {

private final String repoKey, key;

@@ -51,7 +58,7 @@ class DefaultDebtRemediationFunctions implements DebtRemediationFunctions {
private DebtRemediationFunction create(DefaultDebtRemediationFunction.Type type, @Nullable String factor, @Nullable String offset) {
try {
return new DefaultDebtRemediationFunction(type, factor, offset);
} catch (DefaultDebtRemediationFunction.ValidationException e) {
} catch (Exception e) {
throw MessageException.of(String.format("The rule '%s:%s' is invalid : %s ", this.repoKey, this.key, e.getMessage()));
}
}

+ 1
- 1
sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RuleTagFormat.java View File

@@ -40,7 +40,7 @@ public class RuleTagFormat {

public static void validate(String tag) {
if (!isValid(tag)) {
throw new IllegalArgumentException(String.format("Tag '%s' is invalid. Rule tags accept only the following characters: a-z, 0-9, '+', '-', '#', '.'", tag));
throw new IllegalArgumentException(String.format("Tag '%s' is invalid. Rule tags accept only the characters: a-z, 0-9, '+', '-', '#', '.'", tag));
}
}
}

+ 101
- 5
sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java View File

@@ -33,6 +33,7 @@ import org.slf4j.LoggerFactory;
import org.sonar.api.ServerExtension;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
import org.sonar.api.server.debt.DebtRemediationFunction;

import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
@@ -49,6 +50,89 @@ import java.util.Set;
* this extension point in order to define the rules that it supports.
* <p/>
* This interface replaces the deprecated class org.sonar.api.rules.RuleRepository.
* <p/>
* <h3>How to use</h3>
* <pre>
* public class JsSquidRulesDefinition implements RulesDefinition {
*
* {@literal @}Override
* public void define(Context context) {
* NewRepository repository = context.createRepository("js_squid", "js").setName("Javascript Squid");
*
* // define a rule programmatically. Note that rules
* // could be loaded from files (JSON, XML, ...)
* NewRule x1Rule = repository.createRule("x1")
* .setName("No empty line")
* .setHtmlDescription("Generate an issue on empty lines")
*
* // optional tags
* .setTags("style", "stupid")
*
* // optional status. Default value is READY.
* .setStatus(RuleStatus.BETA)
*
* // default severity when the rule is activated on a Quality profile. Default value is MAJOR.
* .setSeverity(Severity.MINOR);
*
* x1Rule
* .setDebtCharacteristic("INTEGRATION_TESTABILITY")
* .setDebtRemediationFunction(x1Rule.debtRemediationFunctions().linearWithOffset("1h", "30min"));
*
* x1Rule.createParam("acceptWhitespace")
* .setDefaultValue("false")
* .setType(RuleParamType.BOOLEAN)
* .setDescription("Accept whitespaces on the line");
*
* // don't forget to call done() to finalize the definition
* repository.done();
* }
* }
* </pre>
* <p/>
* If rules are declared in a XML file with the standard SonarQube format, then it can be loaded by using :
*
* <pre>
* public class JsSquidRulesDefinition implements RulesDefinition {
*
* private final RulesDefinitionXmlLoader xmlLoader;
*
* public JsSquidRulesDefinition(RulesDefinitionXmlLoader xmlLoader) {
* this.xmlLoader = xmlLoader;
* }
*
* {@literal @}Override
* public void define(Context context) {
* NewRepository repository = context.createRepository("js_squid", "js").setName("Javascript Squid");
* // see javadoc of RulesDefinitionXmlLoader for the format
* xmlLoader.load(repository, getClass().getResourceAsStream("/path/to/rules.xml"));
* repository.done();
* }
* }
* </pre>
*
* In the above example, XML file must contain name and description of each rule. If it's not the case, then the
* (deprecated) English bundles can be used :
*
* <pre>
* public class JsSquidRulesDefinition implements RulesDefinition {
*
* private final RulesDefinitionXmlLoader xmlLoader;
* private final RulesDefinitionI18nLoader i18nLoader;
*
* public JsSquidRulesDefinition(RulesDefinitionXmlLoader xmlLoader, RulesDefinitionI18nLoader i18nLoader) {
* this.xmlLoader = xmlLoader;
* this.i18nLoader = i18nLoader;
* }
*
* {@literal @}Override
* public void define(Context context) {
* NewRepository repository = context.createRepository("js_squid", "js").setName("Javascript Squid");
* xmlLoader.load(repository, getClass().getResourceAsStream("/path/to/rules.xml"));
* i18nLoader.load(repository);
* repository.done();
* }
* }
* </pre>
*
* @since 4.3
*/
@@ -278,6 +362,15 @@ public interface RulesDefinition extends ServerExtension {
}
}

interface DebtRemediationFunctions {
DebtRemediationFunction linear(String factor);

DebtRemediationFunction linearWithOffset(String factor, String offset);

DebtRemediationFunction constantPerIssue(String offset);
}


class NewRule {
private final String repoKey, key;
private String name, htmlDescription, internalKey, severity = Severity.MAJOR;
@@ -288,7 +381,7 @@ public interface RulesDefinition extends ServerExtension {
private String effortToFixDescription;
private final Set<String> tags = Sets.newTreeSet();
private final Map<String, NewParam> paramsByKey = Maps.newHashMap();
private final DefaultDebtRemediationFunctions functions;
private final DebtRemediationFunctions functions;

private NewRule(String repoKey, String key) {
this.repoKey = repoKey;
@@ -356,13 +449,16 @@ public interface RulesDefinition extends ServerExtension {
return functions;
}

public NewRule setDebtRemediationFunction(@Nullable DebtRemediationFunction debtRemediationFunction) {
this.debtRemediationFunction = debtRemediationFunction;
/**
* @see #debtRemediationFunctions()
*/
public NewRule setDebtRemediationFunction(@Nullable DebtRemediationFunction fn) {
this.debtRemediationFunction = fn;
return this;
}

public NewRule setEffortToFixDescription(@Nullable String effortToFixDescription) {
this.effortToFixDescription = effortToFixDescription;
public NewRule setEffortToFixDescription(@Nullable String s) {
this.effortToFixDescription = s;
return this;
}


+ 70
- 0
sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinitionI18nLoader.java View File

@@ -0,0 +1,70 @@
/*
* 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.api.server.rule;

import org.apache.commons.lang.StringUtils;
import org.sonar.api.ServerComponent;
import org.sonar.api.i18n.RuleI18n;

/**
* Loads the English bundles of rules (name, description and parameters) that are
* deprecated since 4.2. It can be useful when loading existing XML files that
* do not contain rule names and descriptions.
* <p/>
* This class must be executed after declaring rules on {@link RulesDefinition.NewRepository}.
* <p/>
* Note that localization of rules was dropped in version 4.2. All texts are English.
*
* @see org.sonar.api.server.rule.RulesDefinition for an example
* @since 4.3
*/
public class RulesDefinitionI18nLoader implements ServerComponent {

private final RuleI18n i18n;

public RulesDefinitionI18nLoader(RuleI18n i18n) {
this.i18n = i18n;
}

/**
* Loads descriptions of rules and related rule parameters. Existing descriptions
* are overridden if English labels exist in bundles.
*/
public void load(RulesDefinition.NewRepository repo) {
for (RulesDefinition.NewRule rule : repo.rules()) {
String name = i18n.getName(repo.key(), rule.key());
if (StringUtils.isNotBlank(name)) {
rule.setName(name);
}

String desc = i18n.getDescription(repo.key(), rule.key());
if (StringUtils.isNotBlank(desc)) {
rule.setHtmlDescription(desc);
}

for (RulesDefinition.NewParam param : rule.params()) {
String paramDesc = i18n.getParamDescription(repo.key(), rule.key(), param.key());
if (StringUtils.isNotBlank(paramDesc)) {
param.setDescription(paramDesc);
}
}
}
}
}

+ 2
- 2
sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java View File

@@ -39,7 +39,7 @@ import java.util.Map;
* <p/>
* The classes implementing this extension point must be declared in {@link org.sonar.api.SonarPlugin#getExtensions()}.
*
* <h2>How to use</h2>
* <h3>How to use</h3>
* <pre>
* public class HelloWs implements WebService {
* {@literal @}Override
@@ -66,7 +66,7 @@ import java.util.Map;
* }
* }
* </pre>
* <h2>How to unit test</h2>
* <h3>How to test</h3>
* <pre>
* public class HelloWsTest {
* WebService ws = new HelloWs();

sonar-plugin-api/src/test/java/org/sonar/api/server/rule/DefaultDebtRemediationFunctionTest.java → sonar-plugin-api/src/test/java/org/sonar/api/server/debt/DefaultDebtRemediationFunctionTest.java View File

@@ -18,9 +18,11 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.api.server.rule;
package org.sonar.api.server.debt;

import org.junit.Ignore;
import org.junit.Test;
import org.sonar.api.server.debt.internal.DefaultDebtRemediationFunction;

import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.Fail.fail;
@@ -28,7 +30,7 @@ import static org.fest.assertions.Fail.fail;
public class DefaultDebtRemediationFunctionTest {

@Test
public void create_linear() throws Exception {
public void create_linear() {
DebtRemediationFunction function = new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR, "10h", null);
assertThat(function.type()).isEqualTo(DefaultDebtRemediationFunction.Type.LINEAR);
assertThat(function.factor()).isEqualTo("10h");
@@ -36,7 +38,7 @@ public class DefaultDebtRemediationFunctionTest {
}

@Test
public void create_linear_with_offset() throws Exception {
public void create_linear_with_offset() {
DebtRemediationFunction function = new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET, "10h", "5min");
assertThat(function.type()).isEqualTo(DefaultDebtRemediationFunction.Type.LINEAR_OFFSET);
assertThat(function.factor()).isEqualTo("10h");
@@ -44,7 +46,7 @@ public class DefaultDebtRemediationFunctionTest {
}

@Test
public void create_constant_per_issue() throws Exception {
public void create_constant_per_issue() {
DebtRemediationFunction function = new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.CONSTANT_ISSUE, null, "10h");
assertThat(function.type()).isEqualTo(DefaultDebtRemediationFunction.Type.CONSTANT_ISSUE);
assertThat(function.factor()).isNull();
@@ -53,74 +55,74 @@ public class DefaultDebtRemediationFunctionTest {

@Test
public void sanitize_remediation_factor_and_offset() {
DebtRemediationFunction function = new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET, " 1 h ", " 10 mi n");
DebtRemediationFunction function = new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET, " 1 h ", " 10 min");

assertThat(function.factor()).isEqualTo("1h");
assertThat(function.offset()).isEqualTo("10min");
}

@Test
public void fail_to_create_linear_when_no_factor() throws Exception {
public void fail_to_create_linear_when_no_factor() {
try {
new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR, null, "10h");
fail();
} catch (Exception e) {
assertThat(e).isInstanceOf(DefaultDebtRemediationFunction.ValidationException.class);
} catch (IllegalArgumentException e) {
assertThat(e).hasMessage("Only factor must be set on DebtRemediationFunction{type=LINEAR, factor=null, offset=10h}");
}
}

@Test
public void fail_to_create_linear_when_offset() throws Exception {
public void fail_to_create_linear_when_offset() {
try {
new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR, "5min", "10h");
fail();
} catch (Exception e) {
assertThat(e).isInstanceOf(DefaultDebtRemediationFunction.ValidationException.class);
} catch (IllegalArgumentException e) {
assertThat(e).hasMessage("Only factor must be set on DebtRemediationFunction{type=LINEAR, factor=5min, offset=10h}");
}
}

@Test
public void fail_to_create_constant_per_issue_when_no_offset() throws Exception {
public void fail_to_create_constant_per_issue_when_no_offset() {
try {
new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.CONSTANT_ISSUE, "10h", null);
fail();
} catch (Exception e) {
assertThat(e).isInstanceOf(DefaultDebtRemediationFunction.ValidationException.class);
} catch (IllegalArgumentException e) {
assertThat(e).hasMessage("Only offset must be set on DebtRemediationFunction{type=CONSTANT_ISSUE, factor=10h, offset=null}");
}
}

@Test
public void fail_to_create_constant_per_issue_when_factor() throws Exception {
public void fail_to_create_constant_per_issue_when_factor() {
try {
new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.CONSTANT_ISSUE, "5min", "10h");
fail();
} catch (Exception e) {
assertThat(e).isInstanceOf(DefaultDebtRemediationFunction.ValidationException.class);
} catch (IllegalArgumentException e) {
assertThat(e).hasMessage("Only offset must be set on DebtRemediationFunction{type=CONSTANT_ISSUE, factor=5min, offset=10h}");
}
}

@Test
public void fail_to_create_linear_with_offset_when_no_factor() throws Exception {
public void fail_to_create_linear_with_offset_when_no_factor() {
try {
new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET, null, "10h");
new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET, "", "10h");
fail();
} catch (Exception e) {
assertThat(e).isInstanceOf(DefaultDebtRemediationFunction.ValidationException.class);
} catch (IllegalArgumentException e) {
assertThat(e).hasMessage("Both factor and offset are required on DebtRemediationFunction{type=LINEAR_OFFSET, factor=null, offset=10h}");
}
}

@Test
public void fail_to_create_linear_with_offset_when_no_offset() throws Exception {
public void fail_to_create_linear_with_offset_when_no_offset() {
try {
new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET, "5min", null);
new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET, "5min", "");
fail();
} catch (Exception e) {
assertThat(e).isInstanceOf(DefaultDebtRemediationFunction.ValidationException.class);
} catch (IllegalArgumentException e) {
assertThat(e).hasMessage("Both factor and offset are required on DebtRemediationFunction{type=LINEAR_OFFSET, factor=5min, offset=null}");
}
}

@Test
public void test_equals_and_hashcode() throws Exception {
public void test_equals_and_hashcode() {
DebtRemediationFunction function = new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET, "10h", "5min");
DebtRemediationFunction functionWithSameValue = new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET, "10h", "5min");
DebtRemediationFunction functionWithDifferentType = new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.CONSTANT_ISSUE, null, "5min");
@@ -140,8 +142,20 @@ public class DefaultDebtRemediationFunctionTest {
}

@Test
public void test_to_string() throws Exception {
assertThat(new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET, "10h", "5min").toString()).isNotNull();
public void test_to_string() {
assertThat(new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET, "10h", "5min").toString())
.isEqualTo("DebtRemediationFunction{type=LINEAR_OFFSET, factor=10h, offset=5min}");
}

@Ignore
@Test
public void fail_if_bad_factor_format() {
try {
new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR, "foo", null);
fail();
} catch (IllegalArgumentException e) {
assertThat(e).hasMessage("TODO");
}

}
}

+ 1
- 1
sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RuleTagFormatTest.java View File

@@ -53,7 +53,7 @@ public class RuleTagFormatTest {
RuleTagFormat.validate(" ");
fail();
} catch (IllegalArgumentException e) {
assertThat(e).hasMessage("Tag ' ' is invalid. Rule tags accept only the following characters: a-z, 0-9, '+', '-', '#', '.'");
assertThat(e).hasMessage("Tag ' ' is invalid. Rule tags accept only the characters: a-z, 0-9, '+', '-', '#', '.'");
}
}
}

+ 99
- 0
sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionI18nLoaderTest.java View File

@@ -0,0 +1,99 @@
/*
* 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.api.server.rule;

import org.junit.Test;
import org.sonar.api.i18n.RuleI18n;

import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class RulesDefinitionI18nLoaderTest {

RuleI18n i18n = mock(RuleI18n.class);
RulesDefinitionI18nLoader loader = new RulesDefinitionI18nLoader(i18n);

@Test
public void complete_rule_name_and_description() throws Exception {
when(i18n.getName("squid", "S0001")).thenReturn("SOne");
when(i18n.getDescription("squid", "S0001")).thenReturn("S One");

RulesDefinition.Context context = new RulesDefinition.Context();
RulesDefinition.NewRepository repo = context.createRepository("squid", "java");
// rule without description
repo.createRule("S0001");

loader.load(repo);
repo.done();

RulesDefinition.Rule rule = context.repository("squid").rule("S0001");
assertThat(rule.name()).isEqualTo("SOne");
assertThat(rule.htmlDescription()).isEqualTo("S One");
}

@Test
public void do_not_override_if_no_bundle() throws Exception {
// i18n returns null values

RulesDefinition.Context context = new RulesDefinition.Context();
RulesDefinition.NewRepository repo = context.createRepository("squid", "java");
repo.createRule("S0001").setName("SOne").setHtmlDescription("S One");

loader.load(repo);
repo.done();

RulesDefinition.Rule rule = context.repository("squid").rule("S0001");
assertThat(rule.name()).isEqualTo("SOne");
assertThat(rule.htmlDescription()).isEqualTo("S One");
}

@Test
public void override_existing() throws Exception {
when(i18n.getName("squid", "S0001")).thenReturn("SOne");
when(i18n.getDescription("squid", "S0001")).thenReturn("S One");

RulesDefinition.Context context = new RulesDefinition.Context();
RulesDefinition.NewRepository repo = context.createRepository("squid", "java");
repo.createRule("S0001").setName("Bad").setHtmlDescription("Bad");

loader.load(repo);
repo.done();

RulesDefinition.Rule rule = context.repository("squid").rule("S0001");
assertThat(rule.name()).isEqualTo("SOne");
assertThat(rule.htmlDescription()).isEqualTo("S One");
}

@Test
public void complete_param_description() throws Exception {
when(i18n.getParamDescription("squid", "S0001", "max")).thenReturn("Maximum");

RulesDefinition.Context context = new RulesDefinition.Context();
RulesDefinition.NewRepository repo = context.createRepository("squid", "java");
repo.createRule("S0001").setName("SOne").setHtmlDescription("S One").createParam("max");

loader.load(repo);
repo.done();

RulesDefinition.Rule rule = context.repository("squid").rule("S0001");
assertThat(rule.param("max").description()).isEqualTo("Maximum");
}
}

+ 31
- 30
sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionTest.java View File

@@ -22,6 +22,7 @@ package org.sonar.api.server.rule;
import org.junit.Test;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
import org.sonar.api.server.debt.DebtRemediationFunction;

import java.net.URL;

@@ -63,8 +64,8 @@ public class RulesDefinitionTest {

@Test
public void define_rules() {
RulesDefinition.NewRepository newFindbugs = context.createRepository("findbugs", "java");
RulesDefinition.NewRule newFindbug = newFindbugs.createRule("NPE")
RulesDefinition.NewRepository newRepo = context.createRepository("findbugs", "java");
RulesDefinition.NewRule newRule = newRepo.createRule("NPE")
.setName("Detect NPE")
.setHtmlDescription("Detect <code>java.lang.NullPointerException</code>")
.setSeverity(Severity.BLOCKER)
@@ -74,36 +75,36 @@ public class RulesDefinitionTest {
.setEffortToFixDescription("squid.S115.effortToFix")
.setTags("one", "two")
.addTags("two", "three", "four");
newFindbug.setDebtRemediationFunction(newFindbug.debtRemediationFunctions().linearWithOffset("1h", "10min"));
newRule.setDebtRemediationFunction(newRule.debtRemediationFunctions().linearWithOffset("1h", "10min"));

newFindbugs.createRule("ABC").setName("ABC").setHtmlDescription("ABC");
newFindbugs.done();
newRepo.createRule("ABC").setName("ABC").setHtmlDescription("ABC");
newRepo.done();

RulesDefinition.Repository findbugs = context.repository("findbugs");
assertThat(findbugs.rules()).hasSize(2);
RulesDefinition.Rule npeRule = findbugs.rule("NPE");
assertThat(npeRule.key()).isEqualTo("NPE");
assertThat(npeRule.name()).isEqualTo("Detect NPE");
assertThat(npeRule.severity()).isEqualTo(Severity.BLOCKER);
assertThat(npeRule.htmlDescription()).isEqualTo("Detect <code>java.lang.NullPointerException</code>");
assertThat(npeRule.tags()).containsOnly("one", "two", "three", "four");
assertThat(npeRule.params()).isEmpty();
assertThat(npeRule.internalKey()).isEqualTo("/something");
assertThat(npeRule.template()).isFalse();
assertThat(npeRule.status()).isEqualTo(RuleStatus.BETA);
assertThat(npeRule.debtCharacteristic()).isEqualTo("COMPILER");
assertThat(npeRule.debtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET);
assertThat(npeRule.debtRemediationFunction().factor()).isEqualTo("1h");
assertThat(npeRule.debtRemediationFunction().offset()).isEqualTo("10min");
assertThat(npeRule.effortToFixDescription()).isEqualTo("squid.S115.effortToFix");
assertThat(npeRule.toString()).isEqualTo("[repository=findbugs, key=NPE]");
assertThat(npeRule.repository()).isSameAs(findbugs);
RulesDefinition.Repository repo = context.repository("findbugs");
assertThat(repo.rules()).hasSize(2);
RulesDefinition.Rule rule = repo.rule("NPE");
assertThat(rule.key()).isEqualTo("NPE");
assertThat(rule.name()).isEqualTo("Detect NPE");
assertThat(rule.severity()).isEqualTo(Severity.BLOCKER);
assertThat(rule.htmlDescription()).isEqualTo("Detect <code>java.lang.NullPointerException</code>");
assertThat(rule.tags()).containsOnly("one", "two", "three", "four");
assertThat(rule.params()).isEmpty();
assertThat(rule.internalKey()).isEqualTo("/something");
assertThat(rule.template()).isFalse();
assertThat(rule.status()).isEqualTo(RuleStatus.BETA);
assertThat(rule.debtCharacteristic()).isEqualTo("COMPILER");
assertThat(rule.debtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET);
assertThat(rule.debtRemediationFunction().factor()).isEqualTo("1h");
assertThat(rule.debtRemediationFunction().offset()).isEqualTo("10min");
assertThat(rule.effortToFixDescription()).isEqualTo("squid.S115.effortToFix");
assertThat(rule.toString()).isEqualTo("[repository=findbugs, key=NPE]");
assertThat(rule.repository()).isSameAs(repo);

// test equals() and hashCode()
RulesDefinition.Rule otherRule = findbugs.rule("ABC");
assertThat(npeRule).isEqualTo(npeRule).isNotEqualTo(otherRule).isNotEqualTo("NPE").isNotEqualTo(null);
assertThat(npeRule.hashCode()).isEqualTo(npeRule.hashCode());
RulesDefinition.Rule otherRule = repo.rule("ABC");
assertThat(rule).isEqualTo(rule).isNotEqualTo(otherRule).isNotEqualTo("NPE").isNotEqualTo(null);
assertThat(rule.hashCode()).isEqualTo(rule.hashCode());
}

@Test
@@ -239,14 +240,14 @@ public class RulesDefinitionTest {
fail();
} catch (Exception e) {
assertThat(e).isInstanceOf(IllegalArgumentException.class)
.hasMessage("Tag 'coding style' is invalid. Rule tags accept only the following characters: a-z, 0-9, '+', '-', '#', '.'");
.hasMessage("Tag 'coding style' is invalid. Rule tags accept only the characters: a-z, 0-9, '+', '-', '#', '.'");
}
}

@Test
public void load_rule_description_from_file() {
RulesDefinition.NewRepository newRepository = context.createRepository("findbugs", "java");
newRepository.createRule("NPE").setName("NPE").setHtmlDescription(getClass().getResource("/org/sonar/api/server/rule/RuleDefinitionsTest/sample.html"));
newRepository.createRule("NPE").setName("NPE").setHtmlDescription(getClass().getResource("/org/sonar/api/server/rule/RulesDefinitionTest/sample.html"));
newRepository.done();

RulesDefinition.Rule rule = context.repository("findbugs").rule("NPE");

sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RuleDefinitionsTest/sample.html → sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RulesDefinitionTest/sample.html View File


+ 1
- 1
sonar-server/src/main/java/org/sonar/server/debt/DebtModelBackup.java View File

@@ -30,7 +30,7 @@ import org.sonar.api.ServerComponent;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.server.debt.DebtCharacteristic;
import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic;
import org.sonar.api.server.rule.DebtRemediationFunction;
import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.ValidationMessages;
import org.sonar.core.permission.GlobalPermissions;

+ 1
- 1
sonar-server/src/main/java/org/sonar/server/debt/DebtModelXMLExporter.java View File

@@ -29,7 +29,7 @@ import org.apache.commons.lang.StringUtils;
import org.sonar.api.ServerComponent;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.server.debt.DebtCharacteristic;
import org.sonar.api.server.rule.DebtRemediationFunction;
import org.sonar.api.server.debt.DebtRemediationFunction;
import org.xml.sax.InputSource;

import javax.annotation.CheckForNull;

+ 1
- 1
sonar-server/src/main/java/org/sonar/server/debt/DebtRulesXMLImporter.java View File

@@ -31,7 +31,7 @@ import org.codehaus.staxmate.in.SMHierarchicCursor;
import org.codehaus.staxmate.in.SMInputCursor;
import org.sonar.api.ServerComponent;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.server.rule.DebtRemediationFunction;
import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.utils.Duration;
import org.sonar.api.utils.ValidationMessages;


+ 1
- 1
sonar-server/src/main/java/org/sonar/server/rule/RuleRegistration.java View File

@@ -31,7 +31,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.Rule;
import org.sonar.api.server.rule.DebtRemediationFunction;
import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.api.utils.MessageException;
import org.sonar.api.utils.System2;

+ 1
- 1
sonar-server/src/test/java/org/sonar/server/debt/DebtModelBackupTest.java View File

@@ -32,7 +32,7 @@ import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic;
import org.sonar.api.server.rule.DebtRemediationFunction;
import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.ValidationMessages;

+ 1
- 1
sonar-server/src/test/java/org/sonar/server/debt/DebtModelXMLExporterTest.java View File

@@ -26,7 +26,7 @@ import org.junit.Before;
import org.junit.Test;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic;
import org.sonar.api.server.rule.DebtRemediationFunction;
import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.test.TestUtils;

import java.io.IOException;

+ 2
- 2
sonar-server/src/test/java/org/sonar/server/debt/DebtRulesXMLImporterTest.java View File

@@ -24,7 +24,7 @@ import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import org.junit.Test;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.server.rule.DebtRemediationFunction;
import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.utils.ValidationMessages;

import java.io.IOException;
@@ -211,7 +211,7 @@ public class DebtRulesXMLImporterTest {
RuleDebt ruleDebt = results.get(0);
assertThat(ruleDebt.characteristicKey()).isEqualTo("MEMORY_EFFICIENCY");
assertThat(ruleDebt.ruleKey()).isEqualTo(RuleKey.of("checkstyle", "Regexp"));
assertThat(ruleDebt.function()).isEqualTo(org.sonar.api.server.rule.DebtRemediationFunction.Type.LINEAR);
assertThat(ruleDebt.function()).isEqualTo(DebtRemediationFunction.Type.LINEAR);
assertThat(ruleDebt.factor()).isEqualTo("3h");
assertThat(ruleDebt.offset()).isNull();
}

+ 1
- 1
sonar-server/src/test/java/org/sonar/server/rule/DeprecatedRulesDefinitionTest.java View File

@@ -29,7 +29,7 @@ import org.sonar.api.rule.Severity;
import org.sonar.api.rules.Rule;
import org.sonar.api.rules.RulePriority;
import org.sonar.api.rules.RuleRepository;
import org.sonar.api.server.rule.DebtRemediationFunction;
import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.api.utils.ValidationMessages;
import org.sonar.core.i18n.RuleI18nManager;

Loading…
Cancel
Save