diff options
author | Jenkins CI <ci@sonarsource.com> | 2016-02-12 08:01:23 +0100 |
---|---|---|
committer | Jenkins CI <ci@sonarsource.com> | 2016-02-12 08:01:23 +0100 |
commit | 45ad6ed9b0212c7e542005cb95518bd3f8d52d6f (patch) | |
tree | 462068b53c77788666c9867dbbc341497909f22f /server | |
parent | c885d23b868fe6d038c2e4a557f826e878316fcb (diff) | |
parent | 54cdd41e77bf99b81134435499219eaef4862525 (diff) | |
download | sonarqube-45ad6ed9b0212c7e542005cb95518bd3f8d52d6f.tar.gz sonarqube-45ad6ed9b0212c7e542005cb95518bd3f8d52d6f.zip |
Automatic merge from branch-5.4
* origin/branch-5.4:
Fix shutdown of server after DB migration
SONAR-7103 Fix new debt ratio when 5 periods are defined
SONAR-7311 fix file leak because CE appenders are closed too late
Diffstat (limited to 'server')
6 files changed, 266 insertions, 51 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/log/CeLogging.java b/server/sonar-server/src/main/java/org/sonar/server/computation/log/CeLogging.java index 948949af205..1b746df41ea 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/log/CeLogging.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/log/CeLogging.java @@ -19,11 +19,14 @@ */ package org.sonar.server.computation.log; +import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.sift.MDCBasedDiscriminator; import ch.qos.logback.classic.sift.SiftingAppender; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; +import ch.qos.logback.core.sift.AppenderTracker; +import ch.qos.logback.core.util.Duration; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; import java.io.File; @@ -34,11 +37,13 @@ import org.apache.commons.io.comparator.LastModifiedFileComparator; import org.apache.commons.io.filefilter.FileFilterUtils; import org.apache.log4j.MDC; import org.sonar.api.config.Settings; +import org.sonar.process.LogbackHelper; import org.sonar.process.ProcessProperties; import org.sonar.process.Props; import org.sonar.server.computation.queue.CeTask; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.FluentIterable.from; import static com.google.common.collect.Lists.newArrayList; import static java.lang.String.format; @@ -52,10 +57,16 @@ import static java.lang.String.format; */ public class CeLogging { + private static final long TIMEOUT_2_MINUTES = 1000 * 60 * 2L; + private static final String CE_APPENDER_NAME = "ce"; + // using 0L as timestamp when retrieving appender to stop it will make it instantly eligible for removal + private static final long STOPPING_TRACKER_TIMESTAMP = 0L; + @VisibleForTesting static final String MDC_LOG_PATH = "ceLogPath"; public static final String MAX_LOGS_PROPERTY = "sonar.ce.maxLogsPerTask"; + private final LogbackHelper helper = new LogbackHelper(); private final File logsDir; private final Settings settings; @@ -107,10 +118,18 @@ public class CeLogging { MDC.remove(MDC_LOG_PATH); if (relativePath != null) { + stopAppender(relativePath); purgeDir(new File(logsDir, relativePath).getParentFile()); } } + private void stopAppender(String relativePath) { + Appender<ILoggingEvent> appender = helper.getRootContext().getLogger(Logger.ROOT_LOGGER_NAME).getAppender(CE_APPENDER_NAME); + checkState(appender instanceof SiftingAppender, "Appender with name %s is null or not a SiftingAppender", CE_APPENDER_NAME); + AppenderTracker<ILoggingEvent> ceAppender = ((SiftingAppender) appender).getAppenderTracker(); + ceAppender.getOrCreate(relativePath, STOPPING_TRACKER_TIMESTAMP).stop(); + } + @VisibleForTesting void purgeDir(File dir) { if (dir.exists()) { @@ -161,7 +180,8 @@ public class CeLogging { siftingAppender.setContext(ctx); siftingAppender.setDiscriminator(mdcDiscriminator); siftingAppender.setAppenderFactory(new CeFileAppenderFactory(logsDir)); - siftingAppender.setName("ce"); + siftingAppender.setName(CE_APPENDER_NAME); + siftingAppender.setTimeout(new Duration(TIMEOUT_2_MINUTES)); siftingAppender.start(); return siftingAppender; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/sqale/SqaleNewMeasuresVisitor.java b/server/sonar-server/src/main/java/org/sonar/server/computation/sqale/SqaleNewMeasuresVisitor.java index 531137e8c93..b41c07b6b39 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/sqale/SqaleNewMeasuresVisitor.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/sqale/SqaleNewMeasuresVisitor.java @@ -177,12 +177,12 @@ public class SqaleNewMeasuresVisitor extends PathAwareVisitorAdapter<SqaleNewMea for (Period period : periodsHolder.getPeriods()) { if (isLineInPeriod(changeset.getDate(), period)) { devCostCounter.incrementDevCost(period, lineDevCost); - hasDevCost[period.getIndex()] = true; + hasDevCost[period.getIndex() - 1] = true; } } } for (Period period : periodsHolder.getPeriods()) { - if (hasDevCost[period.getIndex()]) { + if (hasDevCost[period.getIndex() - 1]) { long newDebt = getLongValue(measureRepository.getRawMeasure(file, this.newDebtMetric), period); devCostCounter.incrementNewDebt(period, newDebt); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel.java index 75f29777a22..d060d8bc2c6 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel.java @@ -100,7 +100,7 @@ public abstract class PlatformLevel { */ public PlatformLevel destroy() { if (parent != null) { - parent.container.removeChild(); + parent.container.removeChild(container); } return this; } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/container/ComputeEngineContainerImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/container/ComputeEngineContainerImplTest.java index e37c1856e9c..13abce63cc8 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/container/ComputeEngineContainerImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/container/ComputeEngineContainerImplTest.java @@ -54,7 +54,7 @@ public class ComputeEngineContainerImplTest { ContainerPopulator populator = mock(ContainerPopulator.class); ComputeEngineContainerImpl ceContainer = new ComputeEngineContainerImpl(parent, populator); - assertThat(parent.getChild()).isNull(); + assertThat(parent.getChildren()).isEmpty(); verify(populator).populateContainer(ceContainer); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/log/CeLoggingTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/log/CeLoggingTest.java index d10df360754..7df4d130dc6 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/log/CeLoggingTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/log/CeLoggingTest.java @@ -19,38 +19,65 @@ */ package org.sonar.server.computation.log; +import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.sift.SiftingAppender; import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; import ch.qos.logback.core.filter.Filter; +import ch.qos.logback.core.joran.spi.JoranException; import com.google.common.base.Optional; import java.io.File; import java.io.IOException; +import java.util.Collection; import java.util.List; import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; +import org.slf4j.LoggerFactory; import org.slf4j.MDC; import org.sonar.api.config.Settings; +import org.sonar.process.LogbackHelper; import org.sonar.process.ProcessProperties; import org.sonar.server.computation.queue.CeTask; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.server.computation.log.CeLogging.MDC_LOG_PATH; public class CeLoggingTest { @Rule public ExpectedException expectedException = ExpectedException.none(); - @Rule public TemporaryFolder temp = new TemporaryFolder(); + private LogbackHelper helper = new LogbackHelper(); + private File dataDir; + + @Before + public void setUp() throws Exception { + this.dataDir = temp.newFolder(); + } + + @After + public void resetLogback() throws JoranException { + helper.resetFromXml("/logback-test.xml"); + } + + @After + public void cleanMDC() throws Exception { + MDC.clear(); + } + @Test public void getFile() throws IOException { - File dataDir = temp.newFolder(); Settings settings = newSettings(dataDir, 10); CeLogging underTest = new CeLogging(settings); @@ -73,57 +100,124 @@ public class CeLoggingTest { } @Test - public void use_MDC_to_store_path_to_in_progress_task_logs() throws IOException { - CeLogging underTest = new CeLogging(newSettings(temp.newFolder(), 5)); + public void initForTask_adds_path_of_ce_log_file_in_MDC() throws IOException { + CeLogging underTest = new CeLogging(newSettings(dataDir, 5)); + + CeTask task = createCeTask("TYPE1", "U1"); + underTest.initForTask(task); + assertThat(MDC.get(MDC_LOG_PATH)).isNotEmpty().isEqualTo(LogFileRef.from(task).getRelativePath()); + } + + @Test + public void clearForTask_throws_ISE_if_CE_appender_is_not_configured() throws IOException { + CeLogging underTest = new CeLogging(newSettings(dataDir, 5)); + + CeTask task = createCeTask("TYPE1", "U1"); + underTest.initForTask(task); + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("Appender with name ce is null or not a SiftingAppender"); + + underTest.clearForTask(); + } + + @Test + public void clearForTask_throws_ISE_if_CE_appender_is_not_a_SiftingAppender() throws IOException { + Appender<ILoggingEvent> mockCeAppender = mock(Appender.class); + when(mockCeAppender.getName()).thenReturn("ce"); + helper.getRootContext().getLogger(Logger.ROOT_LOGGER_NAME).addAppender(mockCeAppender); + + CeLogging underTest = new CeLogging(newSettings(dataDir, 5)); + + CeTask task = createCeTask("TYPE1", "U1"); + underTest.initForTask(task); + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("Appender with name ce is null or not a SiftingAppender"); + + underTest.clearForTask(); + } + + @Test + public void clearForTask_clears_MDC() throws IOException { + setupCeAppender(); - CeTask task = new CeTask.Builder().setType("TYPE1").setUuid("U1").build(); + CeLogging underTest = new CeLogging(newSettings(dataDir, 5)); + + CeTask task = createCeTask("TYPE1", "U1"); underTest.initForTask(task); - assertThat(MDC.get(CeLogging.MDC_LOG_PATH)).isNotEmpty().isEqualTo(LogFileRef.from(task).getRelativePath()); + assertThat(MDC.get(MDC_LOG_PATH)).isNotEmpty().isEqualTo(LogFileRef.from(task).getRelativePath()); + + underTest.clearForTask(); + assertThat(MDC.get(MDC_LOG_PATH)).isNull(); + } + + @Test + public void cleanForTask_stops_only_appender_for_MDC_value() throws IOException { + Logger rootLogger = setupCeAppender(); + + CeLogging underTest = new CeLogging(newSettings(dataDir, 5)); + + // init MDC + underTest.initForTask(createCeTask("TYPE1", "U1")); + verifyNoAppender(rootLogger); + + // logging will create and start the appender + LoggerFactory.getLogger(getClass()).info("some log!"); + verifyAllAppenderStarted(rootLogger, 1); + + // init MDC and create appender for another task + // (in the same thread, which should not happen, but it's good enough for our test) + CeTask ceTask = createCeTask("TYPE1", "U2"); + underTest.initForTask(ceTask); + LoggerFactory.getLogger(getClass()).info("some other log!"); + verifyAllAppenderStarted(rootLogger, 2); + + // stop appender which is currently referenced in MDC underTest.clearForTask(); - assertThat(MDC.get(CeLogging.MDC_LOG_PATH)).isNull(); + + Appender appender = verifySingleAppenderIsStopped(rootLogger, 2); + assertThat(appender.getName()).isEqualTo("ce-" + LogFileRef.from(ceTask).getRelativePath()); } @Test public void delete_oldest_files_of_same_directory_to_keep_only_max_allowed_files() throws IOException { - File dir = temp.newFolder(); for (int i = 1; i <= 5; i++) { - File file = new File(dir, format("U%d.log", i)); + File file = new File(dataDir, format("U%d.log", i)); FileUtils.touch(file); // see javadoc: "all platforms support file-modification times to the nearest second, // but some provide more precision" --> increment by second, not by millisecond file.setLastModified(1_450_000_000_000L + i * 1000); } - assertThat(dir.listFiles()).hasSize(5); + assertThat(dataDir.listFiles()).hasSize(5); // keep 3 files in each dir - CeLogging underTest = new CeLogging(newSettings(dir, 3)); - underTest.purgeDir(dir); + CeLogging underTest = new CeLogging(newSettings(dataDir, 3)); + underTest.purgeDir(dataDir); - assertThat(dir.listFiles()).hasSize(3); - assertThat(dir.listFiles()).extracting("name") + assertThat(dataDir.listFiles()).hasSize(3); + assertThat(dataDir.listFiles()).extracting("name") .containsOnly("U3.log", "U4.log", "U5.log"); } @Test public void do_not_delete_files_if_dir_has_less_files_than_max_allowed() throws IOException { - File dir = temp.newFolder(); - FileUtils.touch(new File(dir, "U1.log")); + FileUtils.touch(new File(dataDir, "U1.log")); - CeLogging underTest = new CeLogging(newSettings(dir, 5)); - underTest.purgeDir(dir); + CeLogging underTest = new CeLogging(newSettings(dataDir, 5)); + underTest.purgeDir(dataDir); - assertThat(dir.listFiles()).extracting("name").containsOnly("U1.log"); + assertThat(dataDir.listFiles()).extracting("name").containsOnly("U1.log"); } @Test public void do_not_keep_any_logs() throws IOException { - File dir = temp.newFolder(); - FileUtils.touch(new File(dir, "U1.log")); + FileUtils.touch(new File(dataDir, "U1.log")); - CeLogging underTest = new CeLogging(newSettings(temp.newFolder(), 0)); - underTest.purgeDir(dir); + CeLogging underTest = new CeLogging(newSettings(dataDir, 0)); + underTest.purgeDir(dataDir); - assertThat(dir.listFiles()).isEmpty(); + assertThat(dataDir.listFiles()).isEmpty(); } @Test @@ -131,22 +225,61 @@ public class CeLoggingTest { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Property sonar.ce.maxLogsPerTask must be positive. Got: -1"); - Settings settings = newSettings(temp.newFolder(), -1); + Settings settings = newSettings(dataDir, -1); CeLogging logging = new CeLogging(settings); - logging.purgeDir(temp.newFolder()); + logging.purgeDir(dataDir); } @Test public void createConfiguration() throws Exception { - File logsDir = temp.newFolder(); - SiftingAppender siftingAppender = CeLogging.createAppenderConfiguration(new LoggerContext(), logsDir); + SiftingAppender siftingAppender = CeLogging.createAppenderConfiguration(new LoggerContext(), dataDir); // filter on CE logs List<Filter<ILoggingEvent>> filters = siftingAppender.getCopyOfAttachedFiltersList(); assertThat(filters).hasSize(1); assertThat(filters.get(0)).isInstanceOf(CeLogAcceptFilter.class); - assertThat(siftingAppender.getDiscriminator().getKey()).isEqualTo(CeLogging.MDC_LOG_PATH); + assertThat(siftingAppender.getDiscriminator().getKey()).isEqualTo(MDC_LOG_PATH); + assertThat(siftingAppender.getTimeout().getMilliseconds()).isEqualTo(1000 * 60 * 2); + } + + private Logger setupCeAppender() { + Logger rootLogger = helper.getRootContext().getLogger(Logger.ROOT_LOGGER_NAME); + rootLogger.addAppender(CeLogging.createAppenderConfiguration(helper.getRootContext(), dataDir)); + return rootLogger; + } + + private void verifyNoAppender(Logger rootLogger) { + Collection<Appender<ILoggingEvent>> allAppenders = getAllAppenders(rootLogger); + assertThat(allAppenders).isEmpty(); + } + + private void verifyAllAppenderStarted(Logger rootLogger, int expectedSize) { + Collection<Appender<ILoggingEvent>> allAppenders = getAllAppenders(rootLogger); + assertThat(allAppenders).hasSize(expectedSize); + for (Appender<ILoggingEvent> appender : allAppenders) { + assertThat(appender.isStarted()).isTrue(); + } + } + + private Appender verifySingleAppenderIsStopped(Logger rootLogger, int expectedSize) { + Collection<Appender<ILoggingEvent>> allAppenders = getAllAppenders(rootLogger); + assertThat(allAppenders).hasSize(expectedSize); + Appender res = null; + for (Appender<ILoggingEvent> appender : allAppenders) { + if (!appender.isStarted()) { + assertThat(res).describedAs("More than one appender found stopped").isNull(); + res = appender; + } + } + assertThat(res).describedAs("There should be one stopped appender").isNotNull(); + return res; + } + + private Collection<Appender<ILoggingEvent>> getAllAppenders(Logger rootLogger) { + Appender<ILoggingEvent> ceAppender = rootLogger.getAppender("ce"); + assertThat(ceAppender).isInstanceOf(SiftingAppender.class); + return ((SiftingAppender) ceAppender).getAppenderTracker().allComponents(); } private static Settings newSettings(File dataDir, int maxLogs) { @@ -155,4 +288,8 @@ public class CeLoggingTest { settings.setProperty(CeLogging.MAX_LOGS_PROPERTY, maxLogs); return settings; } + + private static CeTask createCeTask(String type, String uuid) { + return new CeTask.Builder().setType(type).setUuid(uuid).build(); + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/sqale/SqaleNewMeasuresVisitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/sqale/SqaleNewMeasuresVisitorTest.java index 44ea83c022d..4305f8ddd5f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/sqale/SqaleNewMeasuresVisitorTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/sqale/SqaleNewMeasuresVisitorTest.java @@ -25,10 +25,8 @@ 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.server.computation.batch.TreeRootHolderRule; import org.sonar.server.computation.component.Component; @@ -48,7 +46,11 @@ import org.sonar.server.computation.scm.ScmInfoRepositoryRule; 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; import static org.sonar.api.measures.CoreMetrics.NCLOC_DATA_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_SQALE_DEBT_RATIO; +import static org.sonar.api.measures.CoreMetrics.NEW_SQALE_DEBT_RATIO_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_TECHNICAL_DEBT; 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; @@ -74,9 +76,9 @@ public class SqaleNewMeasuresVisitorTest { 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); + .add(NEW_TECHNICAL_DEBT) + .add(NCLOC_DATA) + .add(NEW_SQALE_DEBT_RATIO); @Rule public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository); @Rule @@ -87,15 +89,9 @@ public class SqaleNewMeasuresVisitorTest { private VisitorsCrawler underTest = new VisitorsCrawler(Arrays.<ComponentVisitor>asList(new SqaleNewMeasuresVisitor(metricRepository, measureRepository, scmInfoRepository, 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() { + setTwoPeriods(); treeRootHolder.setRoot(builder(PROJECT, ROOT_REF).build()); underTest.visit(treeRootHolder.getRoot()); @@ -128,13 +124,14 @@ public class SqaleNewMeasuresVisitorTest { @Test public void file_has_0_new_debt_ratio_if_all_scm_dates_are_before_snapshot_dates() { + setTwoPeriods(); 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)); scmInfoRepository.setScmInfo(LANGUAGE_1_FILE_REF, createChangesets(PERIOD_2_SNAPSHOT_DATE - 100, 4)); @@ -147,6 +144,7 @@ public class SqaleNewMeasuresVisitorTest { @Test public void file_has_new_debt_ratio_if_some_scm_dates_are_after_snapshot_dates() { + setTwoPeriods(); when(sqaleRatingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST); setupOneFileAloneInAProject(50, 12, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.WITH_CHANGESET); measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(50, 12)); @@ -159,6 +157,7 @@ public class SqaleNewMeasuresVisitorTest { @Test public void new_debt_ratio_changes_with_language_cost() { + setTwoPeriods(); when(sqaleRatingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST * 10); setupOneFileAloneInAProject(50, 12, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.WITH_CHANGESET); measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(50, 12)); @@ -171,6 +170,7 @@ public class SqaleNewMeasuresVisitorTest { @Test public void new_debt_ratio_changes_with_new_technical_debt() { + setTwoPeriods(); when(sqaleRatingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST); setupOneFileAloneInAProject(500, 120, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.WITH_CHANGESET); measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(500, 120)); @@ -183,6 +183,7 @@ public class SqaleNewMeasuresVisitorTest { @Test public void new_debt_ratio_on_non_file_level_is_based_on_new_technical_debt_of_that_level() { + setTwoPeriods(); when(sqaleRatingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST); setupOneFileAloneInAProject(500, 120, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.WITH_CHANGESET); measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(1200, 820)); @@ -195,6 +196,7 @@ public class SqaleNewMeasuresVisitorTest { @Test public void no_new_debt_ratio_when_file_is_unit_test() { + setTwoPeriods(); when(sqaleRatingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST); setupOneFileAloneInAProject(50, 12, Flag.UT_FILE, Flag.WITH_NCLOC, Flag.WITH_CHANGESET); measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(50, 12)); @@ -207,6 +209,7 @@ public class SqaleNewMeasuresVisitorTest { @Test public void new_debt_ratio_is_0_on_non_file_level_when_all_files_are_unit_test() { + setTwoPeriods(); when(sqaleRatingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST); setupOneFileAloneInAProject(50, 12, Flag.UT_FILE, Flag.WITH_NCLOC, Flag.WITH_CHANGESET); measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(200, 162)); @@ -219,6 +222,7 @@ public class SqaleNewMeasuresVisitorTest { @Test public void new_debt_ratio_is_0_when_file_has_no_changesets() { + setTwoPeriods(); when(sqaleRatingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST); setupOneFileAloneInAProject(50, 12, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.NO_CHANGESET); measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(50, 12)); @@ -231,6 +235,7 @@ public class SqaleNewMeasuresVisitorTest { @Test public void new_debt_ratio_is_0_on_non_file_level_when_no_file_has_changesets() { + setTwoPeriods(); when(sqaleRatingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST); setupOneFileAloneInAProject(50, 12, Flag.SRC_FILE, Flag.WITH_NCLOC, Flag.NO_CHANGESET); measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(200, 162)); @@ -243,6 +248,7 @@ public class SqaleNewMeasuresVisitorTest { @Test public void new_debt_ratio_is_0_when_there_is_no_ncloc_in_file() { + setTwoPeriods(); when(sqaleRatingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST); setupOneFileAloneInAProject(50, 12, Flag.SRC_FILE, Flag.NO_NCLOC, Flag.WITH_CHANGESET); measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(50, 12)); @@ -255,6 +261,7 @@ public class SqaleNewMeasuresVisitorTest { @Test public void new_debt_ratio_is_0_on_non_file_level_when_one_file_has_zero_new_debt_because_of_no_changeset() { + setTwoPeriods(); when(sqaleRatingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST); setupOneFileAloneInAProject(50, 12, Flag.SRC_FILE, Flag.NO_NCLOC, Flag.WITH_CHANGESET); measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(200, 162)); @@ -267,6 +274,7 @@ public class SqaleNewMeasuresVisitorTest { @Test public void new_debt_ratio_is_0_when_ncloc_measure_is_missing() { + setTwoPeriods(); when(sqaleRatingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST); setupOneFileAloneInAProject(50, 12, Flag.SRC_FILE, Flag.MISSING_MEASURE_NCLOC, Flag.WITH_CHANGESET); measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, createNewDebtMeasure(50, 12)); @@ -279,6 +287,7 @@ public class SqaleNewMeasuresVisitorTest { @Test public void leaf_components_always_have_a_measure_when_at_least_one_period_exist_and_ratio_is_computed_from_current_level_new_debt() { + setTwoPeriods(); when(sqaleRatingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST); treeRootHolder.setRoot( builder(PROJECT, ROOT_REF) @@ -291,7 +300,7 @@ public class SqaleNewMeasuresVisitorTest { ).build() ).build() ).build() - ); + ); Measure newDebtMeasure = createNewDebtMeasure(50, 12); measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NEW_TECHNICAL_DEBT_KEY, newDebtMeasure); @@ -311,6 +320,49 @@ public class SqaleNewMeasuresVisitorTest { assertNewDebtRatioValues(ROOT_REF, 83.33, 0); } + @Test + public void new_debt_ratio_is_computed_for_five_periods() throws Exception { + long period1 = 10000L; + long period2 = 20000L; + long period3 = 30000L; + long period4 = 40000L; + long period5 = 50000L; + + periodsHolder.setPeriods( + new Period(1, SOME_PERIOD_MODE, null, period1, SOME_SNAPSHOT_ID), + new Period(2, SOME_PERIOD_MODE, null, period2, SOME_SNAPSHOT_ID), + new Period(3, SOME_PERIOD_MODE, null, period3, SOME_SNAPSHOT_ID), + new Period(4, SOME_PERIOD_MODE, null, period4, SOME_SNAPSHOT_ID), + new Period(5, SOME_PERIOD_MODE, null, period5, SOME_SNAPSHOT_ID)); + + when(sqaleRatingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST); + + treeRootHolder.setRoot( + builder(PROJECT, ROOT_REF) + .addChildren(builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY)).build()) + .build() + ); + + Measure newDebtMeasure = newMeasureBuilder().setVariations(new MeasureVariations(500d, 500d, 500d, 120d, 120d)).createNoValue(); + measureRepository.addRawMeasure(LANGUAGE_1_FILE_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 + scmInfoRepository.setScmInfo(LANGUAGE_1_FILE_REF, createChangesets(period2 - 100, 2, period2 + 100, 2)); + + measureRepository.addRawMeasure(ROOT_REF, NEW_TECHNICAL_DEBT_KEY, + newMeasureBuilder().setVariations(new MeasureVariations(1200d, 1200d, 1200d, 820d, 820d)).createNoValue()); + + underTest.visit(treeRootHolder.getRoot()); + + assertThat(measureRepository.getAddedRawMeasure(LANGUAGE_1_FILE_REF, NEW_SQALE_DEBT_RATIO_KEY)) + .hasVariation1(833.333, VARIATION_COMPARISON_OFFSET) + .hasVariation2(833.333, VARIATION_COMPARISON_OFFSET) + .hasVariation3(0d, VARIATION_COMPARISON_OFFSET) + .hasVariation4(0d, VARIATION_COMPARISON_OFFSET) + .hasVariation5(0d, VARIATION_COMPARISON_OFFSET); + } + 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); @@ -322,7 +374,7 @@ public class SqaleNewMeasuresVisitorTest { 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); @@ -399,13 +451,19 @@ public class SqaleNewMeasuresVisitorTest { } private void assertNoNewDebtRatioMeasure(int componentRef) { - assertThat(measureRepository.getAddedRawMeasure(componentRef, CoreMetrics.NEW_SQALE_DEBT_RATIO_KEY)) + assertThat(measureRepository.getAddedRawMeasure(componentRef, 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)) + assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_SQALE_DEBT_RATIO_KEY)) .hasVariation2(expectedPeriod2Value, VARIATION_COMPARISON_OFFSET) .hasVariation4(expectedPeriod4Value, VARIATION_COMPARISON_OFFSET); } + + private void setTwoPeriods() { + 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)); + } } |