@@ -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; |
@@ -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(); | |||
@@ -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(); | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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())); | |||
} | |||
} |
@@ -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)); | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
@@ -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); | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -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(); |
@@ -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"); | |||
} | |||
} | |||
} |
@@ -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, '+', '-', '#', '.'"); | |||
} | |||
} | |||
} |
@@ -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"); | |||
} | |||
} |
@@ -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"); |
@@ -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; |
@@ -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; |
@@ -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; | |||
@@ -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; |
@@ -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; |
@@ -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; |
@@ -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(); | |||
} |
@@ -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; |