diff options
author | Julien Lancelot <julien.lancelot@sonarsource.com> | 2014-03-11 08:09:01 +0100 |
---|---|---|
committer | Julien Lancelot <julien.lancelot@sonarsource.com> | 2014-03-11 08:09:01 +0100 |
commit | dce23fcf9ac907f6621ab0c11c397febff280ebc (patch) | |
tree | f819d4a7216d35389a3063b0f0a96cde49cb62db /sonar-batch | |
parent | 1ffa320fb3e44cdaf635678a04c473ca4f348514 (diff) | |
download | sonarqube-dce23fcf9ac907f6621ab0c11c397febff280ebc.tar.gz sonarqube-dce23fcf9ac907f6621ab0c11c397febff280ebc.zip |
SONAR-5056 Read debt from rule during analysis
Diffstat (limited to 'sonar-batch')
18 files changed, 699 insertions, 530 deletions
diff --git a/sonar-batch/src/main/java/org/sonar/batch/DefaultTimeMachine.java b/sonar-batch/src/main/java/org/sonar/batch/DefaultTimeMachine.java index 88ce12563d1..6e6f000d511 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/DefaultTimeMachine.java +++ b/sonar-batch/src/main/java/org/sonar/batch/DefaultTimeMachine.java @@ -32,7 +32,6 @@ import org.sonar.api.measures.MetricFinder; import org.sonar.api.resources.Qualifiers; import org.sonar.api.resources.Resource; import org.sonar.api.technicaldebt.batch.Characteristic; -import org.sonar.api.technicaldebt.batch.Requirement; import org.sonar.api.technicaldebt.batch.TechnicalDebtModel; import org.sonar.batch.index.DefaultIndex; @@ -65,8 +64,7 @@ public class DefaultTimeMachine implements TimeMachine { MeasureModel model = (MeasureModel) object[0]; Integer characteristicId = model.getCharacteristicId(); Characteristic characteristic = techDebtModel.characteristicById(characteristicId); - Requirement requirement = techDebtModel.requirementsById(characteristicId); - Measure measure = toMeasure(model, metricById.get(model.getMetricId()), characteristic != null ? characteristic : null, requirement != null ? requirement : null); + Measure measure = toMeasure(model, metricById.get(model.getMetricId()), characteristic); measure.setDate((Date) object[1]); result.add(measure); } @@ -154,7 +152,7 @@ public class DefaultTimeMachine implements TimeMachine { return result; } - static Measure toMeasure(MeasureModel model, Metric metric, @Nullable Characteristic characteristic, @Nullable Requirement requirement) { + static Measure toMeasure(MeasureModel model, Metric metric, @Nullable Characteristic characteristic) { // NOTE: measures on rule are not supported Measure measure = new Measure(metric); measure.setId(model.getId()); @@ -171,7 +169,6 @@ public class DefaultTimeMachine implements TimeMachine { measure.setVariation5(model.getVariationValue5()); measure.setUrl(model.getUrl()); measure.setCharacteristic(characteristic); - measure.setRequirement(requirement); measure.setPersonId(model.getPersonId()); return measure; } diff --git a/sonar-batch/src/main/java/org/sonar/batch/debt/DebtModelLoader.java b/sonar-batch/src/main/java/org/sonar/batch/debt/DebtModelLoader.java deleted file mode 100644 index 1cbf38d6835..00000000000 --- a/sonar-batch/src/main/java/org/sonar/batch/debt/DebtModelLoader.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2013 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.batch.debt; - -import org.sonar.api.BatchComponent; -import org.sonar.api.rule.RuleKey; -import org.sonar.api.rules.Rule; -import org.sonar.api.rules.RuleFinder; -import org.sonar.api.rules.RuleQuery; -import org.sonar.api.technicaldebt.batch.TechnicalDebtModel; -import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic; -import org.sonar.core.technicaldebt.DefaultTechnicalDebtModel; -import org.sonar.core.technicaldebt.db.CharacteristicDao; -import org.sonar.core.technicaldebt.db.CharacteristicDto; - -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import static com.google.common.collect.Maps.newHashMap; - -public class DebtModelLoader implements BatchComponent { - - private final CharacteristicDao dao; - private final RuleFinder ruleFinder; - - public DebtModelLoader(CharacteristicDao dao, RuleFinder ruleFinder) { - this.dao = dao; - this.ruleFinder = ruleFinder; - } - - public TechnicalDebtModel load() { - DefaultTechnicalDebtModel model = new DefaultTechnicalDebtModel(); - List<CharacteristicDto> dtos = dao.selectEnabledCharacteristics(); - Map<Integer, DefaultCharacteristic> characteristicsById = newHashMap(); - - addRootCharacteristics(model, dtos, characteristicsById); - addCharacteristics(dtos, characteristicsById); - addRequirements(dtos, characteristicsById); - return model; - } - - private void addRootCharacteristics(DefaultTechnicalDebtModel model, List<CharacteristicDto> dtos, Map<Integer, DefaultCharacteristic> characteristicsById) { - for (CharacteristicDto dto : dtos) { - if (dto.getParentId() == null) { - DefaultCharacteristic rootCharacteristic = dto.toCharacteristic(null); - model.addRootCharacteristic(rootCharacteristic); - characteristicsById.put(dto.getId(), rootCharacteristic); - } - } - } - - private void addCharacteristics(List<CharacteristicDto> dtos, Map<Integer, DefaultCharacteristic> characteristicsById) { - for (CharacteristicDto dto : dtos) { - if (dto.getParentId() != null && dto.getRuleId() == null) { - DefaultCharacteristic parent = characteristicsById.get(dto.getParentId()); - DefaultCharacteristic characteristic = dto.toCharacteristic(parent); - characteristicsById.put(dto.getId(), characteristic); - } - } - } - - private void addRequirements(List<CharacteristicDto> dtos, Map<Integer, DefaultCharacteristic> characteristicsById) { - Map<Integer, Rule> rulesById = rulesById(ruleFinder.findAll(RuleQuery.create())); - for (CharacteristicDto dto : dtos) { - Integer ruleId = dto.getRuleId(); - if (ruleId != null) { - DefaultCharacteristic characteristic = characteristicsById.get(dto.getParentId()); - DefaultCharacteristic rootCharacteristic = characteristicsById.get(dto.getRootId()); - Rule rule = rulesById.get(ruleId); - RuleKey ruleKey = RuleKey.of(rule.getRepositoryKey(), rule.getKey()); - dto.toRequirement(ruleKey, characteristic, rootCharacteristic); - } - } - } - - private Map<Integer, Rule> rulesById(Collection<Rule> rules) { - Map<Integer, Rule> rulesById = newHashMap(); - for (Rule rule : rules) { - rulesById.put(rule.getId(), rule); - } - return rulesById; - } - -} diff --git a/sonar-batch/src/main/java/org/sonar/batch/debt/DebtModelProvider.java b/sonar-batch/src/main/java/org/sonar/batch/debt/DebtModelProvider.java index fafeade5489..237bfa3d93b 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/debt/DebtModelProvider.java +++ b/sonar-batch/src/main/java/org/sonar/batch/debt/DebtModelProvider.java @@ -24,7 +24,16 @@ import org.picocontainer.injectors.ProviderAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.technicaldebt.batch.TechnicalDebtModel; +import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic; import org.sonar.api.utils.TimeProfiler; +import org.sonar.core.technicaldebt.DefaultTechnicalDebtModel; +import org.sonar.core.technicaldebt.db.CharacteristicDao; +import org.sonar.core.technicaldebt.db.CharacteristicDto; + +import java.util.List; +import java.util.Map; + +import static com.google.common.collect.Maps.newHashMap; public class DebtModelProvider extends ProviderAdapter { @@ -32,12 +41,42 @@ public class DebtModelProvider extends ProviderAdapter { private TechnicalDebtModel model; - public TechnicalDebtModel provide(DebtModelLoader loader) { + public TechnicalDebtModel provide(CharacteristicDao dao) { if (model == null) { TimeProfiler profiler = new TimeProfiler(LOG).start("Loading technical debt model"); - model = loader.load(); + model = load(dao); profiler.stop(); } return model; } + + private TechnicalDebtModel load(CharacteristicDao dao) { + DefaultTechnicalDebtModel model = new DefaultTechnicalDebtModel(); + List<CharacteristicDto> dtos = dao.selectCharacteristics(); + Map<Integer, DefaultCharacteristic> characteristicsById = newHashMap(); + + addRootCharacteristics(model, dtos, characteristicsById); + addCharacteristics(dtos, characteristicsById); + return model; + } + + private void addRootCharacteristics(DefaultTechnicalDebtModel model, List<CharacteristicDto> dtos, Map<Integer, DefaultCharacteristic> characteristicsById) { + for (CharacteristicDto dto : dtos) { + if (dto.getParentId() == null) { + DefaultCharacteristic rootCharacteristic = dto.toCharacteristic(null); + model.addRootCharacteristic(rootCharacteristic); + characteristicsById.put(dto.getId(), rootCharacteristic); + } + } + } + + private void addCharacteristics(List<CharacteristicDto> dtos, Map<Integer, DefaultCharacteristic> characteristicsById) { + for (CharacteristicDto dto : dtos) { + if (dto.getParentId() != null && dto.getRuleId() == null) { + DefaultCharacteristic parent = characteristicsById.get(dto.getParentId()); + DefaultCharacteristic characteristic = dto.toCharacteristic(parent); + characteristicsById.put(dto.getId(), characteristic); + } + } + } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/debt/RuleDebtCalculator.java b/sonar-batch/src/main/java/org/sonar/batch/debt/RuleDebtCalculator.java deleted file mode 100644 index 54798761191..00000000000 --- a/sonar-batch/src/main/java/org/sonar/batch/debt/RuleDebtCalculator.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2013 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.batch.debt; - -import com.google.common.base.Objects; -import org.sonar.api.BatchExtension; -import org.sonar.api.CoreProperties; -import org.sonar.api.config.Settings; -import org.sonar.api.rule.RuleKey; -import org.sonar.api.technicaldebt.batch.Requirement; -import org.sonar.api.technicaldebt.batch.TechnicalDebtModel; -import org.sonar.api.technicaldebt.batch.internal.DefaultRequirement; -import org.sonar.api.utils.internal.WorkDuration; - -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; - -/** - * Computes the remediation cost based on the quality and analysis models. - */ -public class RuleDebtCalculator implements BatchExtension { - - private final TechnicalDebtModel model; - private final Settings settings; - - public RuleDebtCalculator(TechnicalDebtModel model, Settings settings) { - this.model = model; - this.settings = settings; - } - - /** - * Calculate the technical debt from a requirement - */ - @CheckForNull - public Long calculateTechnicalDebt(RuleKey ruleKey, @Nullable Double effortToFix) { - Requirement requirement = model.requirementsByRule(ruleKey); - if (requirement != null) { - if (requirement.function().equals(DefaultRequirement.CONSTANT_ISSUE) && effortToFix != null) { - throw new IllegalArgumentException("Requirement for '" + ruleKey + "' can not use 'Constant/issue' remediation function " + - "because this rule does not have a fixed remediation cost."); - } - return calculateTechnicalDebt(requirement, effortToFix); - } - return null; - } - - private long calculateTechnicalDebt(Requirement requirement, @Nullable Double effortToFix) { - long result = 0L; - - int factorValue = requirement.factorValue(); - if (factorValue > 0) { - int effortToFixValue = Objects.firstNonNull(effortToFix, 1).intValue(); - result = convertValueAndUnitToMinutes(factorValue, requirement.factorUnit()) * effortToFixValue; - } - - int offsetValue = requirement.offsetValue(); - if (offsetValue > 0) { - result += convertValueAndUnitToMinutes(offsetValue, requirement.offsetUnit()); - } - return result; - } - - private long convertValueAndUnitToMinutes(int value, WorkDuration.UNIT unit){ - if (WorkDuration.UNIT.DAYS.equals(unit)) { - return 60L * value * hoursInDay(); - } else if (WorkDuration.UNIT.HOURS.equals(unit)) { - return 60L * value; - } else if (WorkDuration.UNIT.MINUTES.equals(unit)) { - return value; - } - throw new IllegalStateException("Invalid unit : " + unit); - } - - private int hoursInDay(){ - return settings.getInt(CoreProperties.HOURS_IN_DAY); - } - -} diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java b/sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java index d19ad5de7ad..b204cd6605c 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java +++ b/sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java @@ -19,18 +19,19 @@ */ package org.sonar.batch.issue; +import com.google.common.base.Objects; import com.google.common.base.Strings; +import org.sonar.api.batch.rule.ActiveRule; +import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.batch.rule.Rule; +import org.sonar.api.batch.rule.Rules; import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.profiles.RulesProfile; import org.sonar.api.resources.Project; +import org.sonar.api.rule.RemediationFunction; import org.sonar.api.rule.RuleKey; -import org.sonar.api.rules.ActiveRule; -import org.sonar.api.rules.Rule; -import org.sonar.api.rules.RuleFinder; import org.sonar.api.rules.Violation; import org.sonar.api.utils.Duration; import org.sonar.api.utils.MessageException; -import org.sonar.batch.debt.RuleDebtCalculator; import org.sonar.core.issue.DefaultIssueBuilder; import javax.annotation.Nullable; @@ -40,20 +41,18 @@ import javax.annotation.Nullable; */ public class ModuleIssues { - private final RulesProfile qProfile; + private final ActiveRules activeRules; + private final Rules rules; private final IssueCache cache; private final Project project; private final IssueFilters filters; - private final RuleDebtCalculator technicalDebtCalculator; - private final RuleFinder ruleFinder; - public ModuleIssues(RulesProfile qProfile, IssueCache cache, Project project, IssueFilters filters, RuleDebtCalculator technicalDebtCalculator, RuleFinder ruleFinder) { - this.qProfile = qProfile; + public ModuleIssues(ActiveRules activeRules, Rules rules, IssueCache cache, Project project, IssueFilters filters) { + this.activeRules = activeRules; + this.rules = rules; this.cache = cache; this.project = project; this.filters = filters; - this.technicalDebtCalculator = technicalDebtCalculator; - this.ruleFinder = ruleFinder; } public boolean initAndAddIssue(DefaultIssue issue) { @@ -78,10 +77,10 @@ public class ModuleIssues { private boolean initAndAddIssue(DefaultIssue issue, @Nullable Violation violation) { RuleKey ruleKey = issue.ruleKey(); - Rule rule = ruleFinder.findByKey(ruleKey); + Rule rule = rules.find(ruleKey); validateRule(issue, rule); - ActiveRule activeRule = qProfile.getActiveRule(ruleKey.repository(), ruleKey.rule()); - if (activeRule == null || activeRule.getRule() == null) { + ActiveRule activeRule = activeRules.find(ruleKey); + if (activeRule == null) { // rule does not exist or is not enabled -> ignore the issue return false; } @@ -93,27 +92,48 @@ public class ModuleIssues { return false; } - private void validateRule(DefaultIssue issue, Rule rule){ + private void validateRule(DefaultIssue issue, Rule rule) { RuleKey ruleKey = issue.ruleKey(); if (rule == null) { throw MessageException.of(String.format("The rule '%s' does not exist.", ruleKey)); } - if (Strings.isNullOrEmpty(rule.getName()) && Strings.isNullOrEmpty(issue.message())) { + if (Strings.isNullOrEmpty(rule.name()) && Strings.isNullOrEmpty(issue.message())) { throw MessageException.of(String.format("The rule '%s' has no name and the related issue has no message.", ruleKey)); } } - private void updateIssue(DefaultIssue issue, Rule rule, ActiveRule activeRule ){ + private void updateIssue(DefaultIssue issue, Rule rule, ActiveRule activeRule) { if (Strings.isNullOrEmpty(issue.message())) { - issue.setMessage(rule.getName()); + issue.setMessage(rule.name()); } issue.setCreationDate(project.getAnalysisDate()); issue.setUpdateDate(project.getAnalysisDate()); if (issue.severity() == null) { - issue.setSeverity(activeRule.getSeverity().name()); + issue.setSeverity(activeRule.severity()); } - Long debt = technicalDebtCalculator.calculateTechnicalDebt(issue.ruleKey(), issue.effortToFix()); - issue.setDebt(debt != null ? Duration.create(debt) : null); + if (rule.characteristic() != null) { + issue.setDebt(calculateDebt(rule, issue.effortToFix())); + } + } + + private Duration calculateDebt(Rule rule, @Nullable Double effortToFix) { + if (RemediationFunction.CONSTANT_ISSUE.equals(rule.function()) && effortToFix != null) { + throw new IllegalArgumentException("Rule '" + rule.key() + "' can not use 'Constant/issue' remediation function " + + "because this rule does not have a fixed remediation cost."); + } + Duration result = Duration.create(0); + Duration factor = rule.factor(); + Duration offset = rule.offset(); + + if (factor != null) { + int effortToFixValue = Objects.firstNonNull(effortToFix, 1).intValue(); + result = rule.factor().multiply(effortToFixValue); + } + + if (offset != null) { + result = result.add(offset); + } + return result; } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/rule/RulesProvider.java b/sonar-batch/src/main/java/org/sonar/batch/rule/RulesProvider.java new file mode 100644 index 00000000000..c36b70c0e3b --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/rule/RulesProvider.java @@ -0,0 +1,120 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.batch.rule; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; +import org.picocontainer.injectors.ProviderAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.rule.Rules; +import org.sonar.api.batch.rule.internal.NewRule; +import org.sonar.api.batch.rule.internal.RulesBuilder; +import org.sonar.api.rule.RemediationFunction; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rule.RuleStatus; +import org.sonar.api.technicaldebt.batch.Characteristic; +import org.sonar.api.technicaldebt.batch.TechnicalDebtModel; +import org.sonar.api.utils.Durations; +import org.sonar.api.utils.TimeProfiler; +import org.sonar.core.rule.RuleDao; +import org.sonar.core.rule.RuleDto; +import org.sonar.core.rule.RuleParamDto; + +import javax.annotation.Nullable; + +import java.util.List; + +/** + * Loads all enabled and non manual rules + */ +public class RulesProvider extends ProviderAdapter { + + private static final Logger LOG = LoggerFactory.getLogger(RulesProvider.class); + + private Rules singleton = null; + + public Rules provide(RuleDao ruleDao, TechnicalDebtModel debtModel, Durations durations) { + if (singleton == null) { + TimeProfiler profiler = new TimeProfiler(LOG).start("Loading rules"); + singleton = load(ruleDao, debtModel, durations); + profiler.stop(); + } + return singleton; + } + + private Rules load(RuleDao ruleDao, TechnicalDebtModel debtModel, Durations durations) { + RulesBuilder rulesBuilder = new RulesBuilder(); + + List<RuleParamDto> ruleParamDtos = ruleDao.selectParameters(); + ListMultimap<Integer, RuleParamDto> paramDtosByRuleId = ArrayListMultimap.create(); + for (RuleParamDto dto : ruleParamDtos) { + paramDtosByRuleId.put(dto.getRuleId(), dto); + } + for (RuleDto ruleDto : ruleDao.selectEnablesAndNonManual()) { + RuleKey ruleKey = RuleKey.of(ruleDto.getRepositoryKey(), ruleDto.getRuleKey()); + NewRule newRule = rulesBuilder.add(ruleKey) + .setId(ruleDto.getId()) + .setName(ruleDto.getName()) + .setSeverity(ruleDto.getSeverityString()) + .setDescription(ruleDto.getDescription()) + .setStatus(RuleStatus.valueOf(ruleDto.getStatus())); + // TODO should we set metadata ? + if (ruleDto.getCharacteristicId() != null) { + Characteristic characteristic = characteristic(ruleDto.getCharacteristicId(), ruleKey, debtModel); + updateRuleDebtDefinitions(newRule, ruleKey, characteristic, ruleDto.getRemediationFunction(), ruleDto.getRemediationFactor(), ruleDto.getRemediationOffset(), durations); + } else if (ruleDto.getDefaultCharacteristicId() != null) { + Characteristic characteristic = characteristic(ruleDto.getDefaultCharacteristicId(), ruleKey, debtModel); + updateRuleDebtDefinitions(newRule, ruleKey, characteristic, ruleDto.getDefaultRemediationFunction(), ruleDto.getDefaultRemediationFactor(), + ruleDto.getDefaultRemediationOffset(), durations); + } + for (RuleParamDto ruleParamDto : paramDtosByRuleId.get(ruleDto.getId())) { + newRule.addParam(ruleParamDto.getName()) + .setDescription(ruleParamDto.getDescription()); + } + } + return rulesBuilder.build(); + } + + private void updateRuleDebtDefinitions(NewRule newRule, RuleKey ruleKey, Characteristic characteristic, @Nullable String function, + @Nullable String factor, @Nullable String offset, + Durations durations) { + newRule.setCharacteristic(characteristic.key()); + newRule.setFunction(function(function, ruleKey)); + newRule.setFactor(factor != null ? durations.decode(factor) : null); + newRule.setOffset(offset != null ? durations.decode(offset) : null); + } + + private Characteristic characteristic(Integer characteristicId, RuleKey ruleKey, TechnicalDebtModel debtModel) { + Characteristic characteristic = debtModel.characteristicById(characteristicId); + if (characteristic == null) { + throw new IllegalStateException(String.format("Characteristic id '%s' on rule '%s' has not been found", characteristicId, ruleKey)); + } + return characteristic; + } + + private RemediationFunction function(@Nullable String function, RuleKey ruleKey) { + if (function == null) { + throw new IllegalStateException(String.format("Remediation function should not be null on rule '%s'", ruleKey)); + } + return RemediationFunction.valueOf(function); + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java index 4d3f6876bdd..5e0446c8ae7 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java @@ -37,15 +37,14 @@ import org.sonar.batch.ProjectConfigurator; import org.sonar.batch.ProjectTree; import org.sonar.batch.bootstrap.*; import org.sonar.batch.components.PeriodsDefinition; -import org.sonar.batch.debt.DebtModelLoader; import org.sonar.batch.debt.DebtModelProvider; import org.sonar.batch.debt.IssueChangelogDebtCalculator; -import org.sonar.batch.debt.RuleDebtCalculator; import org.sonar.batch.index.*; import org.sonar.batch.issue.*; import org.sonar.batch.phases.GraphPersister; import org.sonar.batch.profiling.PhasesSumUpTimeProfiler; import org.sonar.batch.qualitygate.ProjectAlerts; +import org.sonar.batch.rule.RulesProvider; import org.sonar.batch.scan.filesystem.InputFileCache; import org.sonar.batch.scan.maven.FakeMavenPluginExecutor; import org.sonar.batch.scan.maven.MavenPluginExecutor; @@ -156,17 +155,19 @@ public class ProjectScanContainer extends ComponentContainer { SymbolizableBuilder.class, // technical debt - DebtModelLoader.class, - RuleDebtCalculator.class, new DebtModelProvider(), + // quality gates + ProjectAlerts.class, + + // rules + new RulesProvider(), + // Differential periods PeriodsDefinition.class, - ProjectSettingsReady.class, - - // quality gates - ProjectAlerts.class); + ProjectSettingsReady.class + ); } private void fixMavenExecutor() { diff --git a/sonar-batch/src/test/java/org/sonar/batch/debt/DebtModelLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/debt/DebtModelProviderTest.java index 2de08c8f77a..50e08fc793c 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/debt/DebtModelLoaderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/debt/DebtModelProviderTest.java @@ -27,34 +27,27 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.sonar.api.rule.RuleKey; import org.sonar.api.rules.Rule; -import org.sonar.api.rules.RuleFinder; -import org.sonar.api.rules.RuleQuery; import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic; -import org.sonar.api.technicaldebt.batch.internal.DefaultRequirement; -import org.sonar.api.utils.internal.WorkDuration; import org.sonar.core.technicaldebt.DefaultTechnicalDebtModel; import org.sonar.core.technicaldebt.db.CharacteristicDao; import org.sonar.core.technicaldebt.db.CharacteristicDto; import static com.google.common.collect.Lists.newArrayList; import static org.fest.assertions.Assertions.assertThat; -import static org.mockito.Matchers.any; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) -public class DebtModelLoaderTest { +public class DebtModelProviderTest { @Mock CharacteristicDao dao; - @Mock - RuleFinder ruleFinder; + DebtModelProvider provider; - DebtModelLoader loader; @Before public void before() { - loader = new DebtModelLoader(dao, ruleFinder); + provider = new DebtModelProvider(); } @Test @@ -83,17 +76,15 @@ public class DebtModelLoaderTest { RuleKey ruleKey = RuleKey.of("checkstyle", "Regexp"); Rule rule = Rule.create(ruleKey.repository(), ruleKey.rule()); rule.setId(100); - when(ruleFinder.findAll(any(RuleQuery.class))).thenReturn(newArrayList(rule)); - when(dao.selectEnabledCharacteristics()).thenReturn(newArrayList(rootCharacteristicDto, characteristicDto, requirementDto)); + when(dao.selectCharacteristics()).thenReturn(newArrayList(rootCharacteristicDto, characteristicDto, requirementDto)); - DefaultTechnicalDebtModel result = (DefaultTechnicalDebtModel) loader.load(); + DefaultTechnicalDebtModel result = (DefaultTechnicalDebtModel) provider.provide(dao); assertThat(result.rootCharacteristics()).hasSize(1); DefaultCharacteristic rootCharacteristic = result.characteristicByKey("MEMORY_EFFICIENCY"); assertThat(rootCharacteristic.key()).isEqualTo("MEMORY_EFFICIENCY"); assertThat(rootCharacteristic.name()).isEqualTo("Memory use"); assertThat(rootCharacteristic.parent()).isNull(); - assertThat(rootCharacteristic.requirements()).isEmpty(); assertThat(rootCharacteristic.children()).hasSize(1); assertThat(rootCharacteristic.children().get(0).key()).isEqualTo("EFFICIENCY"); @@ -102,16 +93,6 @@ public class DebtModelLoaderTest { assertThat(characteristic.name()).isEqualTo("Efficiency"); assertThat(characteristic.parent().key()).isEqualTo("MEMORY_EFFICIENCY"); assertThat(characteristic.children()).isEmpty(); - assertThat(characteristic.requirements()).hasSize(1); - assertThat(characteristic.requirements().get(0).ruleKey()).isEqualTo(ruleKey); - - DefaultRequirement requirement = result.requirementsByRule(ruleKey); - assertThat(requirement.ruleKey()).isEqualTo(ruleKey); - assertThat(requirement.function()).isEqualTo("linear"); - assertThat(requirement.factorValue()).isEqualTo(2); - assertThat(requirement.factorUnit()).isEqualTo(WorkDuration.UNIT.DAYS); - assertThat(requirement.offsetValue()).isEqualTo(0); - assertThat(requirement.offsetUnit()).isEqualTo(WorkDuration.UNIT.MINUTES); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/debt/RuleDebtCalculatorTest.java b/sonar-batch/src/test/java/org/sonar/batch/debt/RuleDebtCalculatorTest.java deleted file mode 100644 index c53cdcdc2d5..00000000000 --- a/sonar-batch/src/test/java/org/sonar/batch/debt/RuleDebtCalculatorTest.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2013 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.batch.debt; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; -import org.sonar.api.CoreProperties; -import org.sonar.api.config.Settings; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.rule.RuleKey; -import org.sonar.api.technicaldebt.batch.TechnicalDebtModel; -import org.sonar.api.technicaldebt.batch.internal.DefaultRequirement; -import org.sonar.api.utils.internal.WorkDuration; - -import static org.fest.assertions.Assertions.assertThat; -import static org.fest.assertions.Fail.fail; -import static org.mockito.Mockito.when; - -@RunWith(MockitoJUnitRunner.class) -public class RuleDebtCalculatorTest { - - private static final int HOURS_IN_DAY = 8; - - @Mock - TechnicalDebtModel model; - - RuleDebtCalculator calculator; - - @Before - public void before() { - calculator = new RuleDebtCalculator(model, new Settings().setProperty(CoreProperties.HOURS_IN_DAY, 8)); - } - - @Test - public void calculate_technical_debt() throws Exception { - RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle"); - DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(ruleKey); - - DefaultRequirement requirement = new DefaultRequirement() - .setFunction("constant_issue") - .setFactorValue(10) - .setFactorUnit(WorkDuration.UNIT.MINUTES) - .setOffsetValue(5) - .setOffsetUnit(WorkDuration.UNIT.MINUTES); - when(model.requirementsByRule(ruleKey)).thenReturn(requirement); - - assertThat(calculator.calculateTechnicalDebt(issue.ruleKey(), issue.effortToFix())).isEqualTo(15L); - } - - @Test - public void calculate_technical_debt_with_effort_to_fix() throws Exception { - RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle"); - DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(ruleKey).setEffortToFix(2d); - - DefaultRequirement requirement = new DefaultRequirement() - .setFunction("linear_offset") - .setFactorValue(10) - .setFactorUnit(WorkDuration.UNIT.MINUTES) - .setOffsetValue(5) - .setOffsetUnit(WorkDuration.UNIT.MINUTES); - when(model.requirementsByRule(ruleKey)).thenReturn(requirement); - - assertThat(calculator.calculateTechnicalDebt(issue.ruleKey(), issue.effortToFix())).isEqualTo(((10 * 2) + 5)); - } - - @Test - public void calculate_technical_debt_with_no_offset() throws Exception { - RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle"); - DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(ruleKey).setEffortToFix(2d); - - DefaultRequirement requirement = new DefaultRequirement() - .setFunction("linear") - .setFactorValue(10) - .setFactorUnit(WorkDuration.UNIT.HOURS); - when(model.requirementsByRule(ruleKey)).thenReturn(requirement); - - assertThat(calculator.calculateTechnicalDebt(issue.ruleKey(), issue.effortToFix())).isEqualTo((10 * 2) * 60); - } - - @Test - public void calculate_technical_debt_with_no_factor() throws Exception { - RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle"); - DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(ruleKey); - - DefaultRequirement requirement = new DefaultRequirement() - .setFunction("constant_issue") - .setOffsetValue(5) - .setOffsetUnit(WorkDuration.UNIT.DAYS); - - when(model.requirementsByRule(ruleKey)).thenReturn(requirement); - - assertThat(calculator.calculateTechnicalDebt(issue.ruleKey(), issue.effortToFix())).isEqualTo(5 * HOURS_IN_DAY * 60); - } - - @Test - public void no_technical_debt_if_requirement_not_found() throws Exception { - RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle"); - DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(ruleKey); - when(model.requirementsByRule(ruleKey)).thenReturn(null); - - assertThat(calculator.calculateTechnicalDebt(issue.ruleKey(), issue.effortToFix())).isNull(); - } - - @Test - public void fail_to_calculate_technical_debt_on_constant_issue_function_with_effort_to_fix() throws Exception { - RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle"); - DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(ruleKey).setEffortToFix(2d); - - DefaultRequirement requirement = new DefaultRequirement() - .setFunction("constant_issue") - .setOffsetValue(5) - .setOffsetUnit(WorkDuration.UNIT.MINUTES); - when(model.requirementsByRule(ruleKey)).thenReturn(requirement); - - try { - assertThat(calculator.calculateTechnicalDebt(issue.ruleKey(), issue.effortToFix())).isEqualTo(15); - fail(); - } catch (Exception e) { - assertThat(e).isInstanceOf(IllegalArgumentException.class) - .hasMessage("Requirement for 'squid:AvoidCycle' can not use 'Constant/issue' remediation function because this rule does not have a fixed remediation cost."); - } - } - -} - diff --git a/sonar-batch/src/test/java/org/sonar/batch/debt/TechnicalDebtModelProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/debt/TechnicalDebtModelProviderTest.java deleted file mode 100644 index 05747f800e5..00000000000 --- a/sonar-batch/src/test/java/org/sonar/batch/debt/TechnicalDebtModelProviderTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2013 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.batch.debt; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; -import org.sonar.api.technicaldebt.batch.TechnicalDebtModel; - -import static org.fest.assertions.Assertions.assertThat; -import static org.mockito.Mockito.*; - -@RunWith(MockitoJUnitRunner.class) -public class TechnicalDebtModelProviderTest { - - @Mock - DebtModelLoader loader; - - @Test - public void load_model() { - TechnicalDebtModel model = mock(TechnicalDebtModel.class); - when(loader.load()).thenReturn(model); - - DebtModelProvider provider = new DebtModelProvider(); - TechnicalDebtModel result = provider.provide(loader); - assertThat(result).isNotNull(); - } - - @Test - public void load_model_only_once() { - TechnicalDebtModel model = mock(TechnicalDebtModel.class); - when(loader.load()).thenReturn(model); - - DebtModelProvider provider = new DebtModelProvider(); - provider.provide(loader); - verify(loader).load(); - - provider.provide(loader); - verifyZeroInteractions(loader); - } -} diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java index 1150211081e..b8c4d1efbf1 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java @@ -26,17 +26,19 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import org.sonar.api.batch.rule.internal.ActiveRulesBuilder; +import org.sonar.api.batch.rule.internal.RulesBuilder; import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.profiles.RulesProfile; import org.sonar.api.resources.JavaFile; import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; +import org.sonar.api.rule.RemediationFunction; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.Severity; -import org.sonar.api.rules.*; +import org.sonar.api.rules.RulePriority; +import org.sonar.api.rules.Violation; import org.sonar.api.utils.Duration; import org.sonar.api.utils.MessageException; -import org.sonar.batch.debt.RuleDebtCalculator; import java.util.Calendar; import java.util.Date; @@ -44,7 +46,6 @@ import java.util.Date; import static org.fest.assertions.Assertions.assertThat; import static org.fest.assertions.Fail.fail; import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.*; @@ -52,25 +53,19 @@ import static org.mockito.Mockito.*; public class ModuleIssuesTest { static final RuleKey SQUID_RULE_KEY = RuleKey.of("squid", "AvoidCycle"); - static final Rule SQUID_RULE = Rule.create("squid", "AvoidCycle").setName("Avoid Cycle"); + static final String SQUID_RULE_NAME = "Avoid Cycle"; @Mock IssueCache cache; @Mock - RulesProfile qProfile; - - @Mock Project project; @Mock IssueFilters filters; - @Mock - RuleDebtCalculator technicalDebtCalculator; - - @Mock - RuleFinder ruleFinder; + ActiveRulesBuilder activeRulesBuilder = new ActiveRulesBuilder(); + RulesBuilder ruleBuilder = new RulesBuilder(); ModuleIssues moduleIssues; @@ -78,13 +73,11 @@ public class ModuleIssuesTest { public void setUp() { when(project.getAnalysisDate()).thenReturn(new Date()); when(project.getEffectiveKey()).thenReturn("org.apache:struts-core"); - - moduleIssues = new ModuleIssues(qProfile, cache, project, filters, technicalDebtCalculator, ruleFinder); } @Test public void fail_on_unknown_rule() throws Exception { - when(ruleFinder.findByKey(SQUID_RULE_KEY)).thenReturn(null); + initModuleIssues(); DefaultIssue issue = new DefaultIssue().setRuleKey(SQUID_RULE_KEY); try { @@ -99,7 +92,8 @@ public class ModuleIssuesTest { @Test public void fail_if_rule_has_no_name_and_issue_has_no_message() throws Exception { - when(ruleFinder.findByKey(SQUID_RULE_KEY)).thenReturn(Rule.create("squid", "AvoidCycle")); + ruleBuilder.add(RuleKey.of("squid", "AvoidCycle")); + initModuleIssues(); DefaultIssue issue = new DefaultIssue().setRuleKey(SQUID_RULE_KEY).setMessage(""); try { @@ -114,8 +108,8 @@ public class ModuleIssuesTest { @Test public void ignore_null_active_rule() throws Exception { - when(qProfile.getActiveRule(anyString(), anyString())).thenReturn(null); - when(ruleFinder.findByKey(SQUID_RULE_KEY)).thenReturn(SQUID_RULE); + ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME); + initModuleIssues(); DefaultIssue issue = new DefaultIssue().setRuleKey(SQUID_RULE_KEY); boolean added = moduleIssues.initAndAddIssue(issue); @@ -126,10 +120,9 @@ public class ModuleIssuesTest { @Test public void ignore_null_rule_of_active_rule() throws Exception { - ActiveRule activeRule = mock(ActiveRule.class); - when(activeRule.getRule()).thenReturn(null); - when(qProfile.getActiveRule(anyString(), anyString())).thenReturn(activeRule); - when(ruleFinder.findByKey(SQUID_RULE_KEY)).thenReturn(SQUID_RULE); + ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME); + activeRulesBuilder.activate(SQUID_RULE_KEY); + initModuleIssues(); DefaultIssue issue = new DefaultIssue().setRuleKey(SQUID_RULE_KEY); boolean added = moduleIssues.initAndAddIssue(issue); @@ -140,12 +133,9 @@ public class ModuleIssuesTest { @Test public void add_issue_to_cache() throws Exception { - Rule rule = SQUID_RULE; - ActiveRule activeRule = mock(ActiveRule.class); - when(activeRule.getRule()).thenReturn(rule); - when(activeRule.getSeverity()).thenReturn(RulePriority.INFO); - when(qProfile.getActiveRule("squid", "AvoidCycle")).thenReturn(activeRule); - when(ruleFinder.findByKey(SQUID_RULE_KEY)).thenReturn(rule); + ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME); + activeRulesBuilder.activate(SQUID_RULE_KEY).setSeverity(Severity.INFO); + initModuleIssues(); Date analysisDate = new Date(); when(project.getAnalysisDate()).thenReturn(analysisDate); @@ -166,13 +156,10 @@ public class ModuleIssuesTest { } @Test - public void use_severity_from_active_rule_if_no_severity() throws Exception { - Rule rule = SQUID_RULE; - ActiveRule activeRule = mock(ActiveRule.class); - when(activeRule.getRule()).thenReturn(rule); - when(activeRule.getSeverity()).thenReturn(RulePriority.INFO); - when(qProfile.getActiveRule("squid", "AvoidCycle")).thenReturn(activeRule); - when(ruleFinder.findByKey(SQUID_RULE_KEY)).thenReturn(rule); + public void use_severity_from_active_rule_if_no_severity_on_issue() throws Exception { + ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME); + activeRulesBuilder.activate(SQUID_RULE_KEY).setSeverity(Severity.INFO); + initModuleIssues(); Date analysisDate = new Date(); when(project.getAnalysisDate()).thenReturn(analysisDate); @@ -189,12 +176,9 @@ public class ModuleIssuesTest { @Test public void use_rule_name_if_no_message() throws Exception { - Rule rule = SQUID_RULE; - ActiveRule activeRule = mock(ActiveRule.class); - when(activeRule.getRule()).thenReturn(rule); - when(activeRule.getSeverity()).thenReturn(RulePriority.INFO); - when(qProfile.getActiveRule("squid", "AvoidCycle")).thenReturn(activeRule); - when(ruleFinder.findByKey(SQUID_RULE_KEY)).thenReturn(rule); + ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME); + activeRulesBuilder.activate(SQUID_RULE_KEY).setSeverity(Severity.INFO); + initModuleIssues(); Date analysisDate = new Date(); when(project.getAnalysisDate()).thenReturn(analysisDate); @@ -216,18 +200,17 @@ public class ModuleIssuesTest { @Test public void add_deprecated_violation() throws Exception { - Rule rule = SQUID_RULE; + ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME); + activeRulesBuilder.activate(SQUID_RULE_KEY).setSeverity(Severity.INFO); + initModuleIssues(); + + org.sonar.api.rules.Rule rule = org.sonar.api.rules.Rule.create("squid", "AvoidCycle", "Avoid Cycle"); Resource resource = new JavaFile("org.struts.Action").setEffectiveKey("struts:org.struts.Action"); Violation violation = new Violation(rule, resource); violation.setLineId(42); violation.setSeverity(RulePriority.CRITICAL); violation.setMessage("the message"); - ActiveRule activeRule = mock(ActiveRule.class); - when(activeRule.getRule()).thenReturn(rule); - when(activeRule.getSeverity()).thenReturn(RulePriority.INFO); - when(qProfile.getActiveRule("squid", "AvoidCycle")).thenReturn(activeRule); - when(ruleFinder.findByKey(SQUID_RULE_KEY)).thenReturn(rule); when(filters.accept(any(DefaultIssue.class), eq(violation))).thenReturn(true); boolean added = moduleIssues.initAndAddViolation(violation); @@ -241,17 +224,14 @@ public class ModuleIssuesTest { assertThat(issue.message()).isEqualTo("the message"); assertThat(issue.key()).isNotEmpty(); assertThat(issue.ruleKey().toString()).isEqualTo("squid:AvoidCycle"); - assertThat(issue.componentKey().toString()).isEqualTo("struts:org.struts.Action"); + assertThat(issue.componentKey()).isEqualTo("struts:org.struts.Action"); } @Test public void filter_issue() throws Exception { - Rule rule = SQUID_RULE; - ActiveRule activeRule = mock(ActiveRule.class); - when(activeRule.getRule()).thenReturn(rule); - when(activeRule.getSeverity()).thenReturn(RulePriority.INFO); - when(qProfile.getActiveRule("squid", "AvoidCycle")).thenReturn(activeRule); - when(ruleFinder.findByKey(SQUID_RULE_KEY)).thenReturn(rule); + ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME); + activeRulesBuilder.activate(SQUID_RULE_KEY).setSeverity(Severity.INFO); + initModuleIssues(); DefaultIssue issue = new DefaultIssue() .setKey("ABCDE") @@ -267,13 +247,14 @@ public class ModuleIssuesTest { } @Test - public void set_remediation_cost() throws Exception { - Rule rule = SQUID_RULE; - ActiveRule activeRule = mock(ActiveRule.class); - when(activeRule.getRule()).thenReturn(rule); - when(activeRule.getSeverity()).thenReturn(RulePriority.INFO); - when(qProfile.getActiveRule("squid", "AvoidCycle")).thenReturn(activeRule); - when(ruleFinder.findByKey(SQUID_RULE_KEY)).thenReturn(rule); + public void set_debt_with_linear_function() throws Exception { + ruleBuilder.add(SQUID_RULE_KEY) + .setName(SQUID_RULE_NAME) + .setCharacteristic("COMPILER_RELATED_PORTABILITY") + .setFunction(RemediationFunction.LINEAR) + .setFactor(Duration.create(10L)); + activeRulesBuilder.activate(SQUID_RULE_KEY).setSeverity(Severity.INFO); + initModuleIssues(); Date analysisDate = new Date(); when(project.getAnalysisDate()).thenReturn(analysisDate); @@ -281,17 +262,104 @@ public class ModuleIssuesTest { DefaultIssue issue = new DefaultIssue() .setKey("ABCDE") .setRuleKey(SQUID_RULE_KEY) - .setSeverity(Severity.CRITICAL); + .setSeverity(Severity.CRITICAL) + .setEffortToFix(2d); + + when(filters.accept(issue, null)).thenReturn(true); + moduleIssues.initAndAddIssue(issue); + + ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class); + verify(cache).put(argument.capture()); + assertThat(argument.getValue().debt()).isEqualTo(Duration.create(20L)); + } + + @Test + public void set_debt_with_linear_with_offset_function() throws Exception { + ruleBuilder.add(SQUID_RULE_KEY) + .setName(SQUID_RULE_NAME) + .setCharacteristic("COMPILER_RELATED_PORTABILITY") + .setFunction(RemediationFunction.LINEAR_OFFSET) + .setFactor(Duration.create(10L)) + .setOffset(Duration.create(25L)); + activeRulesBuilder.activate(SQUID_RULE_KEY).setSeverity(Severity.INFO); + initModuleIssues(); + + Date analysisDate = new Date(); + when(project.getAnalysisDate()).thenReturn(analysisDate); + + DefaultIssue issue = new DefaultIssue() + .setKey("ABCDE") + .setRuleKey(SQUID_RULE_KEY) + .setSeverity(Severity.CRITICAL) + .setEffortToFix(2d); - Long debt = 10L; - when(technicalDebtCalculator.calculateTechnicalDebt(issue.ruleKey(), issue.effortToFix())).thenReturn(debt); when(filters.accept(issue, null)).thenReturn(true); + moduleIssues.initAndAddIssue(issue); + + ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class); + verify(cache).put(argument.capture()); + assertThat(argument.getValue().debt()).isEqualTo(Duration.create(45L)); + } + + @Test + public void set_debt_with_constant_issue_function() throws Exception { + ruleBuilder.add(SQUID_RULE_KEY) + .setName(SQUID_RULE_NAME) + .setCharacteristic("COMPILER_RELATED_PORTABILITY") + .setFunction(RemediationFunction.CONSTANT_ISSUE) + .setOffset(Duration.create(10L)); + activeRulesBuilder.activate(SQUID_RULE_KEY).setSeverity(Severity.INFO); + initModuleIssues(); + + Date analysisDate = new Date(); + when(project.getAnalysisDate()).thenReturn(analysisDate); + DefaultIssue issue = new DefaultIssue() + .setKey("ABCDE") + .setRuleKey(SQUID_RULE_KEY) + .setSeverity(Severity.CRITICAL) + .setEffortToFix(null); + + when(filters.accept(issue, null)).thenReturn(true); moduleIssues.initAndAddIssue(issue); ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class); verify(cache).put(argument.capture()); - assertThat(argument.getValue().debt()).isEqualTo(Duration.create(debt)); + assertThat(argument.getValue().debt()).isEqualTo(Duration.create(10L)); + } + + @Test + public void fail_to_set_debt_with_constant_issue_function_when_effort_to_fix_is_set() throws Exception { + ruleBuilder.add(SQUID_RULE_KEY) + .setName(SQUID_RULE_NAME) + .setCharacteristic("COMPILER_RELATED_PORTABILITY") + .setFunction(RemediationFunction.CONSTANT_ISSUE) + .setOffset(Duration.create(25L)); + activeRulesBuilder.activate(SQUID_RULE_KEY).setSeverity(Severity.INFO); + initModuleIssues(); + + DefaultIssue issue = new DefaultIssue() + .setKey("ABCDE") + .setRuleKey(SQUID_RULE_KEY) + .setSeverity(Severity.CRITICAL) + .setEffortToFix(2d); + + when(filters.accept(issue, null)).thenReturn(true); + + try { + moduleIssues.initAndAddIssue(issue); + fail(); + } catch (Exception e) { + assertThat(e).isInstanceOf(IllegalArgumentException.class) + .hasMessage("Rule 'squid:AvoidCycle' can not use 'Constant/issue' remediation function because this rule does not have a fixed remediation cost."); + } + } + + /** + * Every rules and active rules has to be added in builders before creating ModuleIssues + */ + private void initModuleIssues() { + moduleIssues = new ModuleIssues(activeRulesBuilder.build(), ruleBuilder.build(), cache, project, filters); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/rule/RulesProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/rule/RulesProviderTest.java new file mode 100644 index 00000000000..dc5d9b9e6e9 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/rule/RulesProviderTest.java @@ -0,0 +1,170 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 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.batch.rule; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.sonar.api.batch.rule.Rule; +import org.sonar.api.batch.rule.RuleParam; +import org.sonar.api.batch.rule.Rules; +import org.sonar.api.config.Settings; +import org.sonar.api.rule.RemediationFunction; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rule.Severity; +import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic; +import org.sonar.api.utils.Duration; +import org.sonar.api.utils.Durations; +import org.sonar.core.persistence.AbstractDaoTestCase; +import org.sonar.core.rule.RuleDao; +import org.sonar.core.technicaldebt.DefaultTechnicalDebtModel; + +import static org.fest.assertions.Assertions.assertThat; +import static org.fest.assertions.Fail.fail; + +@RunWith(MockitoJUnitRunner.class) +public class RulesProviderTest extends AbstractDaoTestCase { + + @Mock + Durations durations; + + RuleDao ruleDao; + + DefaultTechnicalDebtModel debtModel; + + RulesProvider provider; + + @Before + public void setUp() throws Exception { + debtModel = new DefaultTechnicalDebtModel(); + debtModel.addRootCharacteristic(new DefaultCharacteristic() + .setId(101) + .setKey("EFFICIENCY") + .setName("Efficiency") + .setParent(new DefaultCharacteristic() + .setId(100) + .setKey("MEMORY_EFFICIENCY") + .setName("Memory use"))); + debtModel.addRootCharacteristic(new DefaultCharacteristic() + .setId(103) + .setKey("PORTABILITY") + .setName("Portability") + .setParent(new DefaultCharacteristic() + .setId(102) + .setKey("COMPILER_RELATED_PORTABILITY") + .setName("Compiler"))); + + durations = new Durations(new Settings().setProperty("sonar.technicalDebt.hoursInDay", 8), null); + ruleDao = new RuleDao(getMyBatis()); + + provider = new RulesProvider(); + } + + @Test + public void build_rules() throws Exception { + setupData("shared"); + + Rules rules = provider.provide(ruleDao, debtModel, durations); + + assertThat(rules.findAll()).hasSize(1); + assertThat(rules.findByRepository("checkstyle")).hasSize(1); + assertThat(rules.findByRepository("unknown")).isEmpty(); + + Rule rule = rules.find(RuleKey.of("checkstyle", "AvoidNull")); + assertThat(rule).isNotNull(); + assertThat(rule.key()).isEqualTo(RuleKey.of("checkstyle", "AvoidNull")); + assertThat(rule.name()).isEqualTo("Avoid Null"); + assertThat(rule.description()).isEqualTo("Should avoid NULL"); + assertThat(rule.severity()).isEqualTo(Severity.MINOR); + assertThat(rule.metadata()).isNull(); + assertThat(rule.params()).hasSize(1); + + RuleParam param = rule.param("myParameter"); + assertThat(param).isNotNull(); + assertThat(param.description()).isEqualTo("My Parameter"); + } + + @Test + public void build_rules_with_default_debt_definitions() throws Exception { + setupData("build_rules_with_default_debt_definitions"); + + Rules rules = provider.provide(ruleDao, debtModel, durations); + + Rule rule = rules.find(RuleKey.of("checkstyle", "AvoidNull")); + assertThat(rule.characteristic()).isEqualTo("EFFICIENCY"); + assertThat(rule.function()).isEqualTo(RemediationFunction.LINEAR_OFFSET); + assertThat(rule.factor()).isEqualTo(Duration.decode("5d", 8)); + assertThat(rule.offset()).isEqualTo(Duration.decode("10h", 8)); + } + + @Test + public void build_rules_with_user_debt_definitions() throws Exception { + setupData("build_rules_with_user_debt_definitions"); + + Rules rules = provider.provide(ruleDao, debtModel, durations); + + Rule rule = rules.find(RuleKey.of("checkstyle", "AvoidNull")); + assertThat(rule.characteristic()).isEqualTo("PORTABILITY"); + assertThat(rule.function()).isEqualTo(RemediationFunction.LINEAR); + assertThat(rule.factor()).isEqualTo(Duration.decode("2h", 8)); + assertThat(rule.offset()).isNull(); + } + + @Test + public void build_rules_with_default_and_user_debt_definitions() throws Exception { + setupData("build_rules_with_default_and_user_debt_definitions"); + + Rules rules = provider.provide(ruleDao, debtModel, durations); + + // As both default columns and user columns on debt are set, user debt columns should be used + Rule rule = rules.find(RuleKey.of("checkstyle", "AvoidNull")); + assertThat(rule.characteristic()).isEqualTo("PORTABILITY"); + assertThat(rule.function()).isEqualTo(RemediationFunction.LINEAR); + assertThat(rule.factor()).isEqualTo(Duration.decode("2h", 8)); + assertThat(rule.offset()).isNull(); + } + + @Test + public void fail_if_characteristic_not_found() throws Exception { + setupData("fail_if_characteristic_not_found"); + + try { + provider.provide(ruleDao, debtModel, durations); + fail(); + } catch (Exception e) { + assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Characteristic id '999' on rule 'checkstyle:AvoidNull' has not been found"); + } + } + + @Test + public void fail_if_no_function() throws Exception { + setupData("fail_if_no_function"); + + try { + provider.provide(ruleDao, debtModel, durations); + fail(); + } catch (Exception e) { + assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Remediation function should not be null on rule 'checkstyle:AvoidNull'"); + } + } +} diff --git a/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/build_rules_with_default_and_user_debt_definitions.xml b/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/build_rules_with_default_and_user_debt_definitions.xml new file mode 100644 index 00000000000..5090b80b139 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/build_rules_with_default_and_user_debt_definitions.xml @@ -0,0 +1,29 @@ +<!-- + ~ SonarQube, open source software quality management tool. + ~ Copyright (C) 2008-2013 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. + --> + +<dataset> + + <rules id="1" plugin_rule_key="AvoidNull" plugin_name="checkstyle" name="Avoid Null" description="Should avoid NULL" status="READY" priority="1" + characteristic_id="103" default_characteristic_id="101" + remediation_function="LINEAR" default_remediation_function="LINEAR_OFFSET" + remediation_factor="2h" default_remediation_factor="5d" + remediation_offset="[null]" default_remediation_offset="10h"/> + +</dataset> diff --git a/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/build_rules_with_default_debt_definitions.xml b/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/build_rules_with_default_debt_definitions.xml new file mode 100644 index 00000000000..024cc36f848 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/build_rules_with_default_debt_definitions.xml @@ -0,0 +1,29 @@ +<!-- + ~ SonarQube, open source software quality management tool. + ~ Copyright (C) 2008-2013 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. + --> + +<dataset> + + <rules id="1" plugin_rule_key="AvoidNull" plugin_name="checkstyle" name="Avoid Null" description="Should avoid NULL" status="READY" priority="1" + characteristic_id="[null]" default_characteristic_id="101" + remediation_function="[null]" default_remediation_function="LINEAR_OFFSET" + remediation_factor="[null]" default_remediation_factor="5d" + remediation_offset="[null]" default_remediation_offset="10h"/> + +</dataset> diff --git a/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/build_rules_with_user_debt_definitions.xml b/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/build_rules_with_user_debt_definitions.xml new file mode 100644 index 00000000000..97b8965b1a8 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/build_rules_with_user_debt_definitions.xml @@ -0,0 +1,29 @@ +<!-- + ~ SonarQube, open source software quality management tool. + ~ Copyright (C) 2008-2013 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. + --> + +<dataset> + + <rules id="1" plugin_rule_key="AvoidNull" plugin_name="checkstyle" name="Avoid Null" description="Should avoid NULL" status="READY" priority="1" + characteristic_id="103" default_characteristic_id="[null]" + remediation_function="LINEAR" default_remediation_function="[null]" + remediation_factor="2h" default_remediation_factor="[null]" + remediation_offset="[null]" default_remediation_offset="[null]"/> + +</dataset> diff --git a/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/fail_if_characteristic_not_found.xml b/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/fail_if_characteristic_not_found.xml new file mode 100644 index 00000000000..005d68415d8 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/fail_if_characteristic_not_found.xml @@ -0,0 +1,29 @@ +<!-- + ~ SonarQube, open source software quality management tool. + ~ Copyright (C) 2008-2013 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. + --> + +<dataset> + + <rules id="1" plugin_rule_key="AvoidNull" plugin_name="checkstyle" name="Avoid Null" description="Should avoid NULL" status="READY" priority="1" + characteristic_id="[null]" default_characteristic_id="999" + remediation_function="[null]" default_remediation_function="LINEAR_OFFSET" + remediation_factor="[null]" default_remediation_factor="5d" + remediation_offset="[null]" default_remediation_offset="10h"/> + +</dataset> diff --git a/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/fail_if_no_function.xml b/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/fail_if_no_function.xml new file mode 100644 index 00000000000..7c069728ff9 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/fail_if_no_function.xml @@ -0,0 +1,29 @@ +<!-- + ~ SonarQube, open source software quality management tool. + ~ Copyright (C) 2008-2013 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. + --> + +<dataset> + + <rules id="1" plugin_rule_key="AvoidNull" plugin_name="checkstyle" name="Avoid Null" description="Should avoid NULL" status="READY" priority="1" + characteristic_id="[null]" default_characteristic_id="101" + remediation_function="[null]" default_remediation_function="[null]" + remediation_factor="[null]" default_remediation_factor="5d" + remediation_offset="[null]" default_remediation_offset="10h"/> + +</dataset> diff --git a/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/shared.xml b/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/shared.xml new file mode 100644 index 00000000000..916cabb500c --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/shared.xml @@ -0,0 +1,31 @@ +<!-- + ~ SonarQube, open source software quality management tool. + ~ Copyright (C) 2008-2013 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. + --> + +<dataset> + + <rules id="1" plugin_rule_key="AvoidNull" plugin_name="checkstyle" name="Avoid Null" description="Should avoid NULL" status="READY" priority="1" + characteristic_id="[null]" default_characteristic_id="101" + remediation_function="[null]" default_remediation_function="LINEAR_OFFSET" + remediation_factor="[null]" default_remediation_factor="5d" + remediation_offset="[null]" default_remediation_offset="10h"/> + + <rules_parameters id="1" rule_id="1" name="myParameter" param_type="plop" default_value="plouf" description="My Parameter"/> + +</dataset> |