--- /dev/null
+sonar.projectKey=sample
+sonar.projectName=Sample
+sonar.projectVersion=1.0-SNAPSHOT
+sonar.sources=src/main/xoo
+sonar.language=xoo
+sonar.scm.provider=xoo
--- /dev/null
+package sample;
+
+// class comment
+public class Sample {
+
+ public Sample(int i) {
+ int j = i++;
+ }
+
+ private String method1() {
+ return "hello";
+ }
+}
--- /dev/null
+ncloc:9
+comment_lines:5
+ncloc_data:1=1;2=0;3=0;4=1;5=0;6=1;7=1;8=1;9=0;10=1;11=1;12=1;13=1;14=0
+classes:1
--- /dev/null
+1,user1,2015-08-01
+1,user1,2015-08-01
+1,user1,2015-08-01
+1,user1,2015-08-01
+1,user1,2015-08-01
+1,user1,2015-08-01
+1,user1,2015-08-01
+1,user1,2015-08-01
+1,user1,2015-08-01
+1,user1,2015-08-01
+1,user1,2015-08-01
+1,user1,2015-08-01
+1,user1,2015-08-01
+1,user1,2015-08-01
--- /dev/null
+sonar.projectKey=sample
+sonar.projectName=Sample
+sonar.projectVersion=1.0-SNAPSHOT
+sonar.sources=src/main/xoo
+sonar.language=xoo
+sonar.scm.provider=xoo
--- /dev/null
+package sample;
+
+// class comment
+public class Sample {
+
+ public Sample(int i) {
+ int j = i++;
+ }
+
+ private String method1() {
+ return "hello";
+ }
+
+ private String method2() {
+ return "hello2";
+ }
+}
--- /dev/null
+ncloc:12
+comment_lines:6
+ncloc_data:1=1;2=0;3=0;4=1;5=0;6=1;7=1;8=1;9=0;10=1;11=1;12=1;13=0;14=1;15=1;16=1;17=1;18=0
+classes:1
--- /dev/null
+1,user1,2015-09-01
+1,user1,2015-09-01
+1,user1,2015-09-01
+1,user1,2015-09-01
+1,user1,2015-09-01
+1,user1,2015-09-01
+1,user1,2015-09-01
+1,user1,2015-09-01
+1,user1,2015-09-01
+1,user1,2015-09-01
+1,user1,2015-09-01
+1,user1,2015-09-01
+2,user2,2015-09-17
+2,user2,2015-09-17
+2,user2,2015-09-17
+2,user2,2015-09-17
+1,user1,2015-09-01
+1,user1,2015-09-01
--- /dev/null
+sonar.projectKey=sample
+sonar.projectName=Sample
+sonar.projectVersion=1.0-SNAPSHOT
+sonar.sources=src/main/xoo
+sonar.language=xoo
+sonar.scm.provider=xoo
--- /dev/null
+package sample;
+
+// class comment
+public class Sample {
+
+ public Sample(int i) {
+ int j = i++;
+ }
+
+ private String method1() {
+ return "hello";
+ }
+
+ private String method2() {
+ return "hello2";
+ }
+
+ private String method3() {
+ String e = "hello3";
+ return e;
+ }
+}
--- /dev/null
+ncloc:16
+comment_lines:7
+ncloc_data:1=1;2=0;3=0;4=1;5=0;6=1;7=1;8=1;9=0;10=1;11=1;12=1;13=0;14=1;15=1;16=1;17=0;18=1;19=1;20=1;21=1;22=1;23=0
+classes:1
--- /dev/null
+1,user1,2015-09-01
+1,user1,2015-09-01
+1,user1,2015-09-01
+1,user1,2015-09-01
+1,user1,2015-09-01
+1,user1,2015-09-01
+1,user1,2015-09-01
+1,user1,2015-09-01
+1,user1,2015-09-01
+1,user1,2015-09-01
+1,user1,2015-09-01
+1,user1,2015-09-01
+2,user2,2015-09-17
+2,user2,2015-09-17
+2,user2,2015-09-17
+2,user2,2015-09-17
+3,user2,2015-09-20
+3,user2,2015-09-20
+3,user2,2015-09-20
+3,user2,2015-09-20
+3,user2,2015-09-20
+1,user1,2015-09-01
+1,user1,2015-09-01
import analysis.suite.measure.CustomMeasuresTest;
import analysis.suite.measure.DifferentialPeriodsTest;
import analysis.suite.measure.MeasureFiltersTest;
+import analysis.suite.measure.NewDebtRatioMeasureTest;
import analysis.suite.measure.TechnicalDebtMeasureVariationTest;
import analysis.suite.measure.TimeMachineTest;
import analysis.suite.testing.CoverageTest;
CoverageTest.class,
NewCoverageTest.class,
TestExecutionTest.class,
- TechnicalDebtMeasureVariationTest.class
+ TechnicalDebtMeasureVariationTest.class,
+ NewDebtRatioMeasureTest.class
})
public class AnalysisTestSuite {
--- /dev/null
+package analysis.suite.measure;
+
+import analysis.suite.AnalysisTestSuite;
+import com.sonar.orchestrator.Orchestrator;
+import com.sonar.orchestrator.locator.FileLocation;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.sonar.wsclient.services.Measure;
+import org.sonar.wsclient.services.Resource;
+import org.sonar.wsclient.services.ResourceQuery;
+import util.ItUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.within;
+
+/**
+ * SONAR-5876
+ */
+public class NewDebtRatioMeasureTest {
+
+ private static final String NEW_DEBT_RATIO_METRIC_KEY = "new_sqale_debt_ratio";
+
+ @ClassRule
+ public static Orchestrator orchestrator = AnalysisTestSuite.ORCHESTRATOR;
+
+ @Before
+ public void cleanUpAnalysisData() {
+ orchestrator.resetData();
+ }
+
+ @Test
+ public void new_debt_ratio_is_computed_from_nes_debt_and_new_ncloc_count_per_file() throws Exception {
+ // This test assumes that period 1 is "since previous analysis" and 2 is "over 30 days"
+
+ // run analysis on the day of after the first commit (2015-09-01), with 'one-issue-per-line' profile
+ // => some issues at date 2015-09-02
+ defineQualityProfile("one-issue-per-line");
+ provisionSampleProject();
+ setSampleProjectQualityProfile("one-issue-per-line");
+ runSampleProjectAnalysis("v1", "sonar.projectDate", "2015-09-02");
+
+ // first analysis, no previous snapshot => periods not resolved => no value
+ assertNoNewDebtRatio();
+
+ // run analysis on the day after of second commit (2015-09-17) 'one-issue-per-line' profile*
+ // => 3 new issues will be created at date 2015-09-18
+ runSampleProjectAnalysis("v2", "sonar.projectDate", "2015-09-18");
+ assertNewDebtRatio(4.44, 4.44);
+
+ // run analysis on the day after of third commit (2015-09-20) 'one-issue-per-line' profile*
+ // => 4 new issues will be created at date 2015-09-21
+ runSampleProjectAnalysis("v3", "sonar.projectDate", "2015-09-21");
+ assertNewDebtRatio(4.17, 4.28);
+ }
+
+ private void assertNoNewDebtRatio() {
+ assertThat(getFileResourceWithVariations(NEW_DEBT_RATIO_METRIC_KEY)).isNull();
+ }
+
+ private void assertNewDebtRatio(@Nullable Double valuePeriod1, @Nullable Double valuePeriod2) {
+ Resource newTechnicalDebt = getFileResourceWithVariations(NEW_DEBT_RATIO_METRIC_KEY);
+ List<Measure> measures = newTechnicalDebt.getMeasures();
+ assertThat(measures.get(0).getVariation1()).isEqualTo(valuePeriod1, within(0.01));
+ assertThat(measures.get(0).getVariation2()).isEqualTo(valuePeriod2, within(0.01));
+ }
+
+ private void setSampleProjectQualityProfile(String qualityProfileKey) {
+ orchestrator.getServer().associateProjectToQualityProfile("sample", "xoo", qualityProfileKey);
+ }
+
+ private void provisionSampleProject() {
+ orchestrator.getServer().provisionProject("sample", "sample");
+ }
+
+ private void defineQualityProfile(String qualityProfileKey) {
+ orchestrator.getServer().restoreProfile(FileLocation.ofClasspath("/measure/suite/" + qualityProfileKey + ".xml"));
+ }
+
+ private void runSampleProjectAnalysis(String projectVersion, String... properties) {
+ ItUtils.runProjectAnalysis(
+ NewDebtRatioMeasureTest.orchestrator,
+ "measure/xoo-new-debt-ratio-" + projectVersion,
+ ItUtils.concat(properties,
+ // disable standard scm support so that it does not interfere with Xoo Scm sensor
+ "sonar.scm.disabled", "false")
+ );
+ }
+
+ private Resource getFileResourceWithVariations(String metricKey) {
+ return orchestrator.getServer().getWsClient().find(ResourceQuery.createForMetrics("sample:src/main/xoo/sample/Sample.xoo", metricKey).setIncludeTrends(true));
+ }
+
+}
*/
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
import com.sonar.orchestrator.Orchestrator;
import com.sonar.orchestrator.build.BuildResult;
import com.sonar.orchestrator.build.SonarRunner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import static com.google.common.collect.FluentIterable.from;
+import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.fail;
import static org.assertj.core.api.Assertions.assertThat;
public static void runProjectAnalysis(Orchestrator orchestrator, String projectRelativePath, String... properties) {
SonarRunner sonarRunner = SonarRunner.create(projectDir(projectRelativePath));
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
- for (int i = 0; i < properties.length; i++) {
+ for (int i = 0; i < properties.length; i+=2) {
builder.put(properties[i], properties[i+1]);
- i+=2;
}
- orchestrator.executeBuild(sonarRunner.setProperties(builder.build()));
+ orchestrator.executeBuild(sonarRunner.setDebugLogs(true).setProperties(builder.build()));
+ }
+
+ /**
+ * Concatenates a vararg to a String array.
+ *
+ * Useful when using {@link #runProjectAnalysis(Orchestrator, String, String...)}, eg.:
+ * <pre>
+ * ItUtils.runProjectAnalysis(orchestrator, "project_name",
+ * ItUtils.concat(properties, "sonar.scm.disabled", "false")
+ * );
+ * </pre>
+ */
+ public static String[] concat(String[] properties, String... str) {
+ if (properties == null || properties.length == 0) {
+ return str;
+ }
+ return from(Iterables.concat(asList(properties), asList(str))).toArray(String.class);
}
}
import org.sonar.server.computation.source.SourceLinesRepositoryImpl;
import org.sonar.server.computation.sqale.SqaleMeasuresVisitor;
import org.sonar.server.computation.sqale.SqaleRatingSettings;
+import org.sonar.server.computation.sqale.SqaleNewMeasuresVisitor;
import org.sonar.server.computation.step.ComputationSteps;
import org.sonar.server.computation.step.ReportComputationSteps;
import org.sonar.server.view.index.ViewIndex;
IntegrateIssuesVisitor.class,
CloseIssuesOnRemovedComponentsVisitor.class,
SqaleMeasuresVisitor.class,
+ SqaleNewMeasuresVisitor.class,
LastCommitVisitor.class,
MeasureComputersVisitor.class,
import static org.sonar.server.computation.component.ComponentVisitor.Order.POST_ORDER;
import static org.sonar.server.computation.measure.Measure.newMeasureBuilder;
-public class SqaleMeasuresVisitor extends PathAwareVisitorAdapter<SqaleMeasuresVisitor.DevelopmentCost> {
+public class SqaleMeasuresVisitor extends PathAwareVisitorAdapter<SqaleMeasuresVisitor.DevelopmentCostCounter> {
private static final Logger LOG = Loggers.get(SqaleMeasuresVisitor.class);
private final MetricRepository metricRepository;
private final Metric sqaleRatingMetric;
public SqaleMeasuresVisitor(MetricRepository metricRepository, MeasureRepository measureRepository, SqaleRatingSettings sqaleRatingSettings) {
- super(CrawlerDepthLimit.LEAVES, POST_ORDER, new SimpleStackElementFactory<DevelopmentCost>() {
- @Override
- public DevelopmentCost createForAny(Component component) {
- return new DevelopmentCost();
- }
-
- /** Counter is not used at ProjectView level, saves on instantiating useless objects */
- @Override
- public DevelopmentCost createForProjectView(Component projectView) {
- return null;
- }
- });
+ super(CrawlerDepthLimit.LEAVES, POST_ORDER, DevelopmentCostCounterFactory.INSTANCE);
this.metricRepository = metricRepository;
this.measureRepository = measureRepository;
this.sqaleRatingSettings = sqaleRatingSettings;
}
@Override
- public void visitProject(Component project, Path<DevelopmentCost> path) {
+ public void visitProject(Component project, Path<DevelopmentCostCounter> path) {
computeAndSaveMeasures(project, path);
}
@Override
- public void visitDirectory(Component directory, Path<DevelopmentCost> path) {
+ public void visitDirectory(Component directory, Path<DevelopmentCostCounter> path) {
computeAndSaveMeasures(directory, path);
}
@Override
- public void visitModule(Component module, Path<DevelopmentCost> path) {
+ public void visitModule(Component module, Path<DevelopmentCostCounter> path) {
computeAndSaveMeasures(module, path);
}
@Override
- public void visitFile(Component file, Path<DevelopmentCost> path) {
+ public void visitFile(Component file, Path<DevelopmentCostCounter> path) {
if (!file.getFileAttributes().isUnitTest()) {
long developmentCosts = computeDevelopmentCost(file);
path.current().add(developmentCosts);
}
@Override
- public void visitView(Component view, Path<DevelopmentCost> path) {
+ public void visitView(Component view, Path<DevelopmentCostCounter> path) {
computeAndSaveMeasures(view, path);
}
@Override
- public void visitSubView(Component subView, Path<DevelopmentCost> path) {
+ public void visitSubView(Component subView, Path<DevelopmentCostCounter> path) {
computeAndSaveMeasures(subView, path);
}
@Override
- public void visitProjectView(Component projectView, Path<DevelopmentCost> path) {
+ public void visitProjectView(Component projectView, Path<DevelopmentCostCounter> path) {
Optional<Measure> developmentCostMeasure = measureRepository.getRawMeasure(projectView, developmentCostMetric);
if (developmentCostMeasure.isPresent()) {
try {
}
}
- private void computeAndSaveMeasures(Component component, Path<DevelopmentCost> path) {
+ private void computeAndSaveMeasures(Component component, Path<DevelopmentCostCounter> path) {
saveDevelopmentCostMeasure(component, path.current());
double density = computeDensity(component, path.current());
increaseParentDevelopmentCost(path);
}
- private void saveDevelopmentCostMeasure(Component component, DevelopmentCost developmentCost) {
+ private void saveDevelopmentCostMeasure(Component component, DevelopmentCostCounter developmentCost) {
// the value of this measure is stored as a string because it can exceed the size limit of number storage on some DB
measureRepository.add(component, developmentCostMetric, newMeasureBuilder().create(Long.toString(developmentCost.getValue())));
}
- private double computeDensity(Component component, DevelopmentCost developmentCost) {
+ private double computeDensity(Component component, DevelopmentCostCounter developmentCost) {
double debt = getLongValue(measureRepository.getRawMeasure(component, technicalDebtMetric));
if (Double.doubleToRawLongBits(developmentCost.getValue()) != 0L) {
return debt / (double) developmentCost.getValue();
measureRepository.add(component, sqaleRatingMetric, newMeasureBuilder().create(rating, ratingLetter));
}
- private void increaseParentDevelopmentCost(Path<DevelopmentCost> path) {
+ private void increaseParentDevelopmentCost(Path<DevelopmentCostCounter> path) {
if (!path.isRoot()) {
// increase parent's developmentCost with our own
path.parent().add(path.current().getValue());
/**
* A wrapper class around a long which can be increased and represents the development cost of a Component
*/
- public static final class DevelopmentCost {
+ public static final class DevelopmentCostCounter {
private long value = 0;
+ private DevelopmentCostCounter() {
+ // prevents instantiation outside SqaleMeasuresVisitor
+ }
+
public void add(long developmentCosts) {
this.value += developmentCosts;
}
return value;
}
}
+
+ private static final class DevelopmentCostCounterFactory extends SimpleStackElementFactory<DevelopmentCostCounter> {
+ public static final DevelopmentCostCounterFactory INSTANCE = new DevelopmentCostCounterFactory();
+
+ private DevelopmentCostCounterFactory() {
+ // prevents instantiation
+ }
+
+ @Override
+ public DevelopmentCostCounter createForAny(Component component) {
+ return new DevelopmentCostCounter();
+ }
+
+ /** Counter is not used at ProjectView level, saves on instantiating useless objects */
+ @Override
+ public DevelopmentCostCounter createForProjectView(Component projectView) {
+ return null;
+ }
+ }
}
--- /dev/null
+/*
+ * 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.server.computation.sqale;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.utils.KeyValueFormat;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.batch.protocol.output.BatchReport;
+import org.sonar.server.computation.batch.BatchReportReader;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.CrawlerDepthLimit;
+import org.sonar.server.computation.component.PathAwareVisitorAdapter;
+import org.sonar.server.computation.formula.counter.LongVariationValue;
+import org.sonar.server.computation.measure.Measure;
+import org.sonar.server.computation.measure.MeasureRepository;
+import org.sonar.server.computation.measure.MeasureVariations;
+import org.sonar.server.computation.metric.Metric;
+import org.sonar.server.computation.metric.MetricRepository;
+import org.sonar.server.computation.period.Period;
+import org.sonar.server.computation.period.PeriodsHolder;
+
+import static com.google.common.collect.FluentIterable.from;
+import static org.sonar.api.utils.KeyValueFormat.newIntegerConverter;
+import static org.sonar.server.computation.component.ComponentVisitor.Order.POST_ORDER;
+import static org.sonar.server.computation.measure.Measure.newMeasureBuilder;
+import static org.sonar.server.computation.measure.MeasureVariations.newMeasureVariationsBuilder;
+
+/**
+ * This visitor depends on {@link org.sonar.server.computation.issue.IntegrateIssuesVisitor} for the computation of
+ * metric {@link CoreMetrics#NEW_TECHNICAL_DEBT}.
+ */
+public class SqaleNewMeasuresVisitor extends PathAwareVisitorAdapter<SqaleNewMeasuresVisitor.NewDevelopmentCostCounter> {
+ private static final Logger LOG = Loggers.get(SqaleNewMeasuresVisitor.class);
+
+ private final BatchReportReader batchReportReader;
+ private final MeasureRepository measureRepository;
+ private final PeriodsHolder periodsHolder;
+ private final SqaleRatingSettings sqaleRatingSettings;
+
+ private final Metric newDebtMetric;
+ private final Metric nclocDataMetric;
+ private final Metric newDebtRatioMetric;
+
+ public SqaleNewMeasuresVisitor(MetricRepository metricRepository, MeasureRepository measureRepository, BatchReportReader batchReportReader, PeriodsHolder periodsHolder,
+ SqaleRatingSettings sqaleRatingSettings) {
+ super(CrawlerDepthLimit.FILE, POST_ORDER, NewDevelopmentCostCounterFactory.INSTANCE);
+ this.batchReportReader = batchReportReader;
+ this.measureRepository = measureRepository;
+ this.periodsHolder = periodsHolder;
+ this.sqaleRatingSettings = sqaleRatingSettings;
+
+ // computed by NewDebtAggregator which is executed by IntegrateIssuesVisitor
+ this.newDebtMetric = metricRepository.getByKey(CoreMetrics.NEW_TECHNICAL_DEBT_KEY);
+ // which line is ncloc and which isn't
+ this.nclocDataMetric = metricRepository.getByKey(CoreMetrics.NCLOC_DATA_KEY);
+ // output metric
+ this.newDebtRatioMetric = metricRepository.getByKey(CoreMetrics.NEW_SQALE_DEBT_RATIO_KEY);
+ }
+
+ @Override
+ public void visitProject(Component project, Path<NewDevelopmentCostCounter> path) {
+ computeAndSaveNewDebtRatioMeasure(project, path);
+ }
+
+ @Override
+ public void visitModule(Component module, Path<NewDevelopmentCostCounter> path) {
+ computeAndSaveNewDebtRatioMeasure(module, path);
+ increaseNewDevCostOfParent(path);
+ }
+
+ @Override
+ public void visitDirectory(Component directory, Path<NewDevelopmentCostCounter> path) {
+ computeAndSaveNewDebtRatioMeasure(directory, path);
+ increaseNewDevCostOfParent(path);
+ }
+
+ @Override
+ public void visitFile(Component file, Path<NewDevelopmentCostCounter> path) {
+ if (file.getFileAttributes().isUnitTest()) {
+ return;
+ }
+
+ initNewDebtRatioCounter(file, path);
+ computeAndSaveNewDebtRatioMeasure(file, path);
+ increaseNewDevCostOfParent(path);
+ }
+
+ private void computeAndSaveNewDebtRatioMeasure(Component component, Path<NewDevelopmentCostCounter> path) {
+ MeasureVariations.Builder builder = newMeasureVariationsBuilder();
+ for (Period period : periodsHolder.getPeriods()) {
+ long newDevCost = path.current().getValue(period).getValue();
+ double density = computeDensity(component, period, newDevCost);
+ builder.setVariation(period, 100.0 * density);
+ }
+ if (!builder.isEmpty()) {
+ Measure measure = newMeasureBuilder().setVariations(builder.build()).createNoValue();
+ measureRepository.add(component, this.newDebtRatioMetric, measure);
+ }
+ }
+
+ private double computeDensity(Component component, Period period, long developmentCost) {
+ double debt = getLongValue(measureRepository.getRawMeasure(component, this.newDebtMetric), period);
+ if (developmentCost != 0L) {
+ return debt / (double) developmentCost;
+ }
+ return 0d;
+ }
+
+ private static long getLongValue(Optional<Measure> measure, Period period) {
+ if (!measure.isPresent()) {
+ return 0L;
+ }
+ return getLongValue(measure.get(), period);
+ }
+
+ private static long getLongValue(Measure measure, Period period) {
+ if (measure.hasVariations() && measure.getVariations().hasVariation(period.getIndex())) {
+ return (long) measure.getVariations().getVariation(period.getIndex());
+ }
+ return 0l;
+ }
+
+ private void initNewDebtRatioCounter(Component file, Path<NewDevelopmentCostCounter> path) {
+ // first analysis, no period, no differential value to compute, save processing time and return now
+ if (periodsHolder.getPeriods().isEmpty()) {
+ return;
+ }
+
+ Optional<Measure> nclocDataMeasure = measureRepository.getRawMeasure(file, this.nclocDataMetric);
+ if (!nclocDataMeasure.isPresent()) {
+ return;
+ }
+
+ long lineDevCost = sqaleRatingSettings.getDevCost(file.getFileAttributes().getLanguageKey());
+ BatchReport.Changesets changesets = batchReportReader.readChangesets(file.getReportAttributes().getRef());
+ if (changesets == null) {
+ LOG.trace(String.format("No changeset for file %s. Dev cost will be zero.", file.getKey()));
+ return;
+ }
+
+ for (Integer nclocLineIndex : nclocLineIndexes(nclocDataMeasure)) {
+ // lines are 0-based in changesetIndexByLine array
+ int changesetIndex = changesets.getChangesetIndexByLine(nclocLineIndex - 1);
+ BatchReport.Changesets.Changeset changeset = changesets.getChangeset(changesetIndex);
+ if (!changeset.hasDate()) {
+ continue;
+ }
+
+ for (Period period : periodsHolder.getPeriods()) {
+ if (isLineInPeriod(changeset.getDate(), period)) {
+ path.current().increment(period, lineDevCost);
+ }
+ }
+ }
+ }
+
+ private static void increaseNewDevCostOfParent(Path<NewDevelopmentCostCounter> path) {
+ path.parent().add(path.current());
+ }
+
+ /**
+ * A line belongs to a Period if its date is older than the SNAPSHOT's date of the period.
+ */
+ private static boolean isLineInPeriod(long lineDate, Period period) {
+ return lineDate > period.getSnapshotDate();
+ }
+
+ /**
+ * NCLOC_DATA contains Key-value pairs, where key - is a number of line, and value - is an indicator of whether line
+ * contains code (1) or not (0).
+ *
+ * This method parses the value of the NCLOC_DATA measure and return the line numbers of lines which contain code.
+ */
+ private static Iterable<Integer> nclocLineIndexes(Optional<Measure> nclocDataMeasure) {
+ Map<Integer, Integer> parsedNclocData = KeyValueFormat.parse(nclocDataMeasure.get().getData(), newIntegerConverter(), newIntegerConverter());
+ return from(parsedNclocData.entrySet())
+ .filter(NclocEntryNclocLine.INSTANCE)
+ .transform(MapEntryToKey.INSTANCE);
+ }
+
+ public static final class NewDevelopmentCostCounter {
+ private final LongVariationValue.Array devCosts = LongVariationValue.newArray();
+
+ public void add(NewDevelopmentCostCounter counter) {
+ this.devCosts.incrementAll(counter.devCosts);
+ }
+
+ public LongVariationValue.Array increment(Period period, long value) {
+ return devCosts.increment(period, value);
+ }
+
+ public LongVariationValue getValue(Period period) {
+ return this.devCosts.get(period);
+ }
+
+ }
+
+ private enum NclocEntryNclocLine implements Predicate<Map.Entry<Integer, Integer>> {
+ INSTANCE;
+
+ @Override
+ public boolean apply(@Nonnull Map.Entry<Integer, Integer> input) {
+ return input.getValue() == 1;
+ }
+ }
+
+ private enum MapEntryToKey implements Function<Map.Entry<Integer, Integer>, Integer> {
+ INSTANCE;
+
+ @Override
+ @Nullable
+ public Integer apply(@Nonnull Map.Entry<Integer, Integer> input) {
+ return input.getKey();
+ }
+ }
+
+ private static class NewDevelopmentCostCounterFactory extends SimpleStackElementFactory<NewDevelopmentCostCounter> {
+ public static final NewDevelopmentCostCounterFactory INSTANCE = new NewDevelopmentCostCounterFactory();
+
+ @Override
+ public NewDevelopmentCostCounter createForAny(Component component) {
+ return new NewDevelopmentCostCounter();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.server.computation.sqale;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Ordering;
+import java.util.Arrays;
+import java.util.Set;
+import org.assertj.core.data.Offset;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.utils.KeyValueFormat;
+import org.sonar.batch.protocol.output.BatchReport;
+import org.sonar.server.computation.batch.BatchReportReaderRule;
+import org.sonar.server.computation.batch.TreeRootHolderRule;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.ComponentVisitor;
+import org.sonar.server.computation.component.FileAttributes;
+import org.sonar.server.computation.component.ReportComponent;
+import org.sonar.server.computation.component.VisitorsCrawler;
+import org.sonar.server.computation.measure.Measure;
+import org.sonar.server.computation.measure.MeasureRepositoryRule;
+import org.sonar.server.computation.measure.MeasureVariations;
+import org.sonar.server.computation.metric.MetricRepositoryRule;
+import org.sonar.server.computation.period.Period;
+import org.sonar.server.computation.period.PeriodsHolderRule;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.measures.CoreMetrics.NCLOC_DATA_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_TECHNICAL_DEBT_KEY;
+import static org.sonar.server.computation.component.Component.Type.DIRECTORY;
+import static org.sonar.server.computation.component.Component.Type.FILE;
+import static org.sonar.server.computation.component.Component.Type.MODULE;
+import static org.sonar.server.computation.component.Component.Type.PROJECT;
+import static org.sonar.server.computation.measure.Measure.newMeasureBuilder;
+import static org.sonar.server.computation.measure.MeasureAssert.assertThat;
+
+public class SqaleNewMeasuresVisitorTest {
+ private static final String LANGUAGE_1_KEY = "language 1 key";
+ private static final long LANGUAGE_1_DEV_COST = 30l;
+ private static final long PERIOD_2_SNAPSHOT_DATE = 12323l;
+ private static final long PERIOD_5_SNAPSHOT_DATE = 99999999l;
+ private static final long SOME_SNAPSHOT_ID = 9993l;
+ private static final String SOME_PERIOD_MODE = "some mode";
+ private static final int ROOT_REF = 1;
+ private static final int LANGUAGE_1_FILE_REF = 11111;
+ private static final Offset<Double> VARIATION_COMPARISON_OFFSET = Offset.offset(0.01);
+
+ @Rule
+ public BatchReportReaderRule reportReader = new BatchReportReaderRule();
+ @Rule
+ public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+ @Rule
+ public MetricRepositoryRule metricRepository = new MetricRepositoryRule()
+ .add(CoreMetrics.NEW_TECHNICAL_DEBT)
+ .add(CoreMetrics.NCLOC_DATA)
+ .add(CoreMetrics.NEW_SQALE_DEBT_RATIO);
+ @Rule
+ public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
+ @Rule
+ public PeriodsHolderRule periodsHolder = new PeriodsHolderRule();
+
+ private SqaleRatingSettings sqaleRatingSettings = mock(SqaleRatingSettings.class);
+
+ private VisitorsCrawler underTest = new VisitorsCrawler(Arrays.<ComponentVisitor>asList(new SqaleNewMeasuresVisitor(metricRepository, measureRepository, reportReader,
+ periodsHolder, sqaleRatingSettings)));
+
+ @Before
+ public void setUp() throws Exception {
+ periodsHolder.setPeriods(
+ new Period(2, SOME_PERIOD_MODE, null, PERIOD_2_SNAPSHOT_DATE, SOME_SNAPSHOT_ID),
+ new Period(4, SOME_PERIOD_MODE, null, PERIOD_5_SNAPSHOT_DATE, SOME_SNAPSHOT_ID));
+ }
+
+ @Test
+ public void project_has_new_debt_ratio_variation_for_each_defined_period() {
+ treeRootHolder.setRoot(builder(PROJECT, ROOT_REF).build());
+
+ underTest.visit(treeRootHolder.getRoot());
+
+ assertNewDebtRatioValues(ROOT_REF, 0, 0);
+ }
+
+ @Test
+ public void project_has_no_new_debt_ratio_variation_if_there_is_no_period() {
+ periodsHolder.setPeriods();
+ treeRootHolder.setRoot(builder(PROJECT, ROOT_REF).build());
+
+ underTest.visit(treeRootHolder.getRoot());
+
+ assertNoNewDebtRatioMeasure(ROOT_REF);
+ }
+
+ @Test
+ public void file_has_no_new_debt_ratio_variation_if_there_is_no_period() {
+ periodsHolder.setPeriods();
+ when(sqaleRatingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST);
+ setupOneFileAloneInAProject(50, 12, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.WITH_CHANGESET);
+
+ underTest.visit(treeRootHolder.getRoot());
+
+ assertNoNewDebtRatioMeasure(LANGUAGE_1_FILE_REF);
+ assertNoNewDebtRatioMeasure(ROOT_REF);
+ }
+
+ @Test
+ public void file_has_0_new_debt_ratio_if_all_scm_dates_are_before_snapshot_dates() {
+ treeRootHolder.setRoot(
+ builder(PROJECT, ROOT_REF)
+ .addChildren(
+ builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY)).build()
+ )
+ .build()
+ );
+ measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(50, 12));
+ measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NCLOC_DATA_KEY, createNclocDataMeasure(2, 3, 4));
+ reportReader.putChangesets(createChangesets(LANGUAGE_1_FILE_REF, PERIOD_2_SNAPSHOT_DATE - 100, 4));
+
+ underTest.visit(treeRootHolder.getRoot());
+
+ assertNewDebtRatioValues(LANGUAGE_1_FILE_REF, 0, 0);
+ assertNewDebtRatioValues(ROOT_REF, 0, 0);
+ }
+
+ @Test
+ public void file_has_new_debt_ratio_if_some_scm_dates_are_after_snapshot_dates() {
+ when(sqaleRatingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST);
+ setupOneFileAloneInAProject(50, 12, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.WITH_CHANGESET);
+
+ underTest.visit(treeRootHolder.getRoot());
+
+ assertNewDebtRatioValues(LANGUAGE_1_FILE_REF, 83.33, 0);
+ assertNewDebtRatioValues(ROOT_REF, 83.33, 0);
+ }
+
+ @Test
+ public void new_debt_ratio_changes_with_language_cost() {
+ when(sqaleRatingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST * 10);
+ setupOneFileAloneInAProject(50, 12, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.WITH_CHANGESET);
+
+ underTest.visit(treeRootHolder.getRoot());
+
+ assertNewDebtRatioValues(LANGUAGE_1_FILE_REF, 8.33, 0);
+ assertNewDebtRatioValues(ROOT_REF, 8.33, 0);
+ }
+
+ @Test
+ public void new_debt_ratio_changes_with_new_technical_debt() {
+ when(sqaleRatingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST);
+ setupOneFileAloneInAProject(500, 120, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.WITH_CHANGESET);
+
+ underTest.visit(treeRootHolder.getRoot());
+
+ assertNewDebtRatioValues(LANGUAGE_1_FILE_REF, 833.33, 0);
+ assertNewDebtRatioValues(ROOT_REF, 833.33, 0);
+ }
+
+ @Test
+ public void no_new_debt_ratio_when_file_is_unit_test() {
+ when(sqaleRatingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST);
+ setupOneFileAloneInAProject(50, 12, Flag.UT_FILE, Flag.WITH_NCLOC, Flag.WITH_CHANGESET);
+
+ underTest.visit(treeRootHolder.getRoot());
+
+ assertNoNewDebtRatioMeasure(LANGUAGE_1_FILE_REF);
+ assertNewDebtRatioValues(ROOT_REF, 0, 0);
+ }
+
+ @Test
+ public void new_debt_ratio_is_0_when_file_has_no_changesets() {
+ when(sqaleRatingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST);
+ setupOneFileAloneInAProject(50, 12, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.NO_CHANGESET);
+
+ underTest.visit(treeRootHolder.getRoot());
+
+ assertNewDebtRatioValues(LANGUAGE_1_FILE_REF, 0, 0);
+ assertNewDebtRatioValues(ROOT_REF, 0, 0);
+ }
+
+ @Test
+ public void new_debt_ratio_is_0_when_file_has_empty_changesets() {
+ when(sqaleRatingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST);
+ setupOneFileAloneInAProject(50, 12, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.NO_DATE_CHANGESET);
+
+ underTest.visit(treeRootHolder.getRoot());
+
+ assertNewDebtRatioValues(LANGUAGE_1_FILE_REF, 0, 0);
+ assertNewDebtRatioValues(ROOT_REF, 0, 0);
+ }
+
+ @Test
+ public void new_debt_ratio_is_0_when_there_is_no_ncloc_in_file() {
+ when(sqaleRatingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST);
+ setupOneFileAloneInAProject(50, 12, Flag.SRC_FILE, Flag.NO_NCLOC, Flag.WITH_CHANGESET);
+
+ underTest.visit(treeRootHolder.getRoot());
+
+ assertNewDebtRatioValues(LANGUAGE_1_FILE_REF, 0, 0);
+ assertNewDebtRatioValues(ROOT_REF, 0, 0);
+ }
+
+ @Test
+ public void new_debt_ratio_is_0_when_ncloc_measure_is_missing() {
+ when(sqaleRatingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST);
+ setupOneFileAloneInAProject(50, 12, Flag.SRC_FILE, Flag.MISSING_MEASURE_NCLOC, Flag.WITH_CHANGESET);
+
+ underTest.visit(treeRootHolder.getRoot());
+
+ assertNewDebtRatioValues(LANGUAGE_1_FILE_REF, 0, 0);
+ assertNewDebtRatioValues(ROOT_REF, 0, 0);
+ }
+
+ @Test
+ public void no_leaf_components_always_have_a_measure_when_at_least_one_period_exist() {
+ when(sqaleRatingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST);
+ treeRootHolder.setRoot(
+ builder(PROJECT, ROOT_REF)
+ .addChildren(
+ builder(MODULE, 11)
+ .addChildren(
+ builder(DIRECTORY, 111)
+ .addChildren(
+ builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY)).build()
+ ).build()
+ ).build()
+ ).build()
+ );
+
+ Measure newDebtMeasure = createNewDebtMeasure(50, 12);
+ measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NEW_TECHNICAL_DEBT_KEY, newDebtMeasure);
+ measureRepository.addRawMeasure(111, NEW_TECHNICAL_DEBT_KEY, newDebtMeasure);
+ measureRepository.addRawMeasure(11, NEW_TECHNICAL_DEBT_KEY, newDebtMeasure);
+ measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, newDebtMeasure);
+ // 4 lines file, only first one is not ncloc
+ measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NCLOC_DATA_KEY, createNclocDataMeasure(2, 3, 4));
+ // first 2 lines are before all snapshots, 2 last lines are after PERIOD 2's snapshot date
+ reportReader.putChangesets(createChangesets(LANGUAGE_1_FILE_REF, PERIOD_2_SNAPSHOT_DATE - 100, 2, PERIOD_2_SNAPSHOT_DATE + 100, 2));
+
+ underTest.visit(treeRootHolder.getRoot());
+
+ assertNewDebtRatioValues(LANGUAGE_1_FILE_REF, 83.33, 0);
+ assertNewDebtRatioValues(111, 83.33, 0);
+ assertNewDebtRatioValues(11, 83.33, 0);
+ assertNewDebtRatioValues(ROOT_REF, 83.33, 0);
+ }
+
+ private void setupOneFileAloneInAProject(int newDebtPeriod2, int newDebtPeriod4, Flag isUnitTest, Flag withNclocLines, Flag withChangeSets) {
+ checkArgument(isUnitTest == Flag.UT_FILE || isUnitTest == Flag.SRC_FILE);
+ checkArgument(withNclocLines == Flag.WITH_NCLOC || withNclocLines == Flag.NO_NCLOC || withNclocLines == Flag.MISSING_MEASURE_NCLOC);
+ checkArgument(withChangeSets == Flag.WITH_CHANGESET || withChangeSets == Flag.NO_CHANGESET || withChangeSets == Flag.NO_DATE_CHANGESET);
+
+ treeRootHolder.setRoot(
+ builder(PROJECT, ROOT_REF)
+ .addChildren(
+ builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(isUnitTest == Flag.UT_FILE, LANGUAGE_1_KEY)).build()
+ )
+ .build()
+ );
+
+ Measure newDebtMeasure = createNewDebtMeasure(newDebtPeriod2, newDebtPeriod4);
+ measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NEW_TECHNICAL_DEBT_KEY, newDebtMeasure);
+ measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, newDebtMeasure);
+ if (withNclocLines == Flag.WITH_NCLOC) {
+ // 4 lines file, only first one is not ncloc
+ measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NCLOC_DATA_KEY, createNclocDataMeasure(2, 3, 4));
+ } else if (withNclocLines == Flag.NO_NCLOC) {
+ // 4 lines file, none of which is ncloc
+ measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NCLOC_DATA_KEY, createNoNclocDataMeasure(4));
+ }
+ if (withChangeSets == Flag.WITH_CHANGESET) {
+ // first 2 lines are before all snapshots, 2 last lines are after PERIOD 2's snapshot date
+ reportReader.putChangesets(createChangesets(LANGUAGE_1_FILE_REF, PERIOD_2_SNAPSHOT_DATE - 100, 2, PERIOD_2_SNAPSHOT_DATE + 100, 2));
+ } else if (withChangeSets == Flag.NO_DATE_CHANGESET) {
+ reportReader.putChangesets(createNoDateChangesets(LANGUAGE_1_FILE_REF, 4));
+ }
+ }
+
+ private enum Flag {
+ UT_FILE, SRC_FILE, NO_CHANGESET, WITH_CHANGESET, NO_DATE_CHANGESET, WITH_NCLOC, NO_NCLOC, MISSING_MEASURE_NCLOC
+ }
+
+ public static ReportComponent.Builder builder(Component.Type type, int ref) {
+ return ReportComponent.builder(type, ref).setKey(String.valueOf(ref));
+ }
+
+ private Measure createNewDebtMeasure(double period2Value, double period4Value) {
+ return newMeasureBuilder().setVariations(new MeasureVariations(null, period2Value, null, period4Value, null)).createNoValue();
+ }
+
+ private static Measure createNclocDataMeasure(Integer... nclocLines) {
+ Set<Integer> nclocLinesSet = ImmutableSet.copyOf(nclocLines);
+ int max = Ordering.<Integer>natural().max(nclocLinesSet);
+ ImmutableMap.Builder<Integer, Integer> builder = ImmutableMap.builder();
+ for (int i = 1; i <= max; i++) {
+ builder.put(i, nclocLinesSet.contains(i) ? 1 : 0);
+ }
+ return newMeasureBuilder().create(KeyValueFormat.format(builder.build(), KeyValueFormat.newIntegerConverter(), KeyValueFormat.newIntegerConverter()));
+ }
+
+ private static Measure createNoNclocDataMeasure(int lineCount) {
+ ImmutableMap.Builder<Integer, Integer> builder = ImmutableMap.builder();
+ for (int i = 1; i <= lineCount; i++) {
+ builder.put(i, 0);
+ }
+ return newMeasureBuilder().create(KeyValueFormat.format(builder.build(), KeyValueFormat.newIntegerConverter(), KeyValueFormat.newIntegerConverter()));
+ }
+
+ /**
+ * Creates a changeset of {@code lines} lines which all have the same date {@code scmDate}.
+ */
+ private static BatchReport.Changesets createChangesets(int componentRef, long scmDate, int lines) {
+ BatchReport.Changesets.Builder builder = BatchReport.Changesets.newBuilder()
+ .setComponentRef(componentRef);
+ addChangeSet(builder, scmDate, lines);
+ return builder.build();
+ }
+
+ /**
+ * Creates a changeset of {@code lineCount} lines which have the date {@code scmDate} and {@code otherLineCount} lines which
+ * have the date {@code otherScmDate}.
+ */
+ private static BatchReport.Changesets createChangesets(int componentRef, long scmDate, int lineCount, long otherScmDate, int otherLineCount) {
+ BatchReport.Changesets.Builder builder = BatchReport.Changesets.newBuilder()
+ .setComponentRef(componentRef);
+ addChangeSet(builder, scmDate, lineCount);
+ addChangeSet(builder, otherScmDate, otherLineCount);
+ return builder.build();
+ }
+
+ private static void addChangeSet(BatchReport.Changesets.Builder builder, long scmDate, int lines) {
+ BatchReport.Changesets.Changeset.Builder changesetBuilder = BatchReport.Changesets.Changeset.newBuilder();
+ changesetBuilder.setRevision("rev" + scmDate);
+ changesetBuilder.setDate(scmDate);
+ builder.addChangeset(changesetBuilder.build());
+ for (int i = 0; i < lines; i++) {
+ builder.addChangesetIndexByLine(builder.getChangesetCount() - 1);
+ }
+ }
+
+ private BatchReport.Changesets createNoDateChangesets(int componentRef, int lineCount) {
+ BatchReport.Changesets.Builder builder = BatchReport.Changesets.newBuilder().setComponentRef(componentRef);
+
+ BatchReport.Changesets.Changeset.Builder changesetBuilder = BatchReport.Changesets.Changeset.newBuilder();
+ changesetBuilder.setRevision("rev");
+ builder.addChangeset(changesetBuilder.build());
+ for (int i = 0; i < lineCount; i++) {
+ builder.addChangesetIndexByLine(builder.getChangesetCount() - 1);
+ }
+
+ return builder.build();
+ }
+
+ private void assertNoNewDebtRatioMeasure(int componentRef) {
+ assertThat(measureRepository.getAddedRawMeasure(componentRef, CoreMetrics.NEW_SQALE_DEBT_RATIO_KEY))
+ .isAbsent();
+ }
+
+ private void assertNewDebtRatioValues(int componentRef, double expectedPeriod2Value, double expectedPeriod4Value) {
+ assertThat(measureRepository.getAddedRawMeasure(componentRef, CoreMetrics.NEW_SQALE_DEBT_RATIO_KEY))
+ .hasVariation2(expectedPeriod2Value, VARIATION_COMPARISON_OFFSET)
+ .hasVariation4(expectedPeriod4Value, VARIATION_COMPARISON_OFFSET);
+ }
+}