From 082546c283020089c17845b37778a0e420a93886 Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Fri, 4 Sep 2015 17:20:26 +0200 Subject: [PATCH] SONAR-6353 new metric days_since_last_commit --- .../computation/source/LastCommitVisitor.java | 16 +- .../source/LastCommitVisitorTest.java | 155 +++++++++++++----- .../org/sonar/api/measures/CoreMetrics.java | 2 +- 3 files changed, 126 insertions(+), 47 deletions(-) diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/source/LastCommitVisitor.java b/server/sonar-server/src/main/java/org/sonar/server/computation/source/LastCommitVisitor.java index a3b9d9a85e2..b667c0d20ca 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/source/LastCommitVisitor.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/source/LastCommitVisitor.java @@ -21,6 +21,7 @@ package org.sonar.server.computation.source; import com.google.common.base.Optional; import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.utils.System2; import org.sonar.batch.protocol.output.BatchReport; import org.sonar.server.computation.batch.BatchReportReader; import org.sonar.server.computation.component.Component; @@ -35,12 +36,16 @@ import static org.sonar.server.computation.component.ComponentVisitor.Order.POST public class LastCommitVisitor extends PathAwareVisitorAdapter { + private static final long MILLISECONDS_PER_DAY = 1000L * 60 * 60 * 24; + private final BatchReportReader reportReader; private final MeasureRepository measureRepository; private final Metric lastCommitDateMetric; + private final Metric daysSinceLastCommitDateMetric; + private final System2 system2; public LastCommitVisitor(BatchReportReader reportReader, MetricRepository metricRepository, - MeasureRepository measureRepository) { + MeasureRepository measureRepository, System2 system2) { super(CrawlerDepthLimit.LEAVES, POST_ORDER, new SimpleStackElementFactory() { @Override public LastCommit createForAny(Component component) { @@ -55,7 +60,9 @@ public class LastCommitVisitor extends PathAwareVisitorAdapter 0L) { measureRepository.add(component, lastCommitDateMetric, Measure.newMeasureBuilder().create(maxDate)); + measureRepository.add(component, daysSinceLastCommitDateMetric, Measure.newMeasureBuilder().create(daysBetween(system2.now(), maxDate))); + if (!path.isRoot()) { path.parent().addDate(maxDate); } } } + private static int daysBetween(long d1, long d2) { + // limitation of metric type: long is not supported yet, so casting to int + return (int) (Math.abs(d1 - d2) / MILLISECONDS_PER_DAY); + } + public static final class LastCommit { private long date = 0; diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/source/LastCommitVisitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/source/LastCommitVisitorTest.java index 93dfebdfa92..429301d0af9 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/source/LastCommitVisitorTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/source/LastCommitVisitorTest.java @@ -21,9 +21,11 @@ package org.sonar.server.computation.source; import com.google.common.base.Optional; import com.google.common.collect.Lists; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.utils.System2; import org.sonar.batch.protocol.output.BatchReport; import org.sonar.server.computation.batch.BatchReportReaderRule; import org.sonar.server.computation.batch.TreeRootHolderRule; @@ -38,6 +40,9 @@ import org.sonar.server.computation.measure.MeasureRepositoryRule; import org.sonar.server.computation.metric.MetricRepositoryRule; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.api.measures.CoreMetrics.DAYS_SINCE_LAST_COMMIT_KEY; import static org.sonar.api.measures.CoreMetrics.LAST_COMMIT_DATE_KEY; import static org.sonar.server.computation.component.Component.Type.DIRECTORY; import static org.sonar.server.computation.component.Component.Type.FILE; @@ -51,7 +56,14 @@ import static org.sonar.server.computation.measure.Measure.newMeasureBuilder; public class LastCommitVisitorTest { - public static final int FILE_REF = 1; + public static final int PROJECT_REF = 1; + public static final int MODULE_REF = 2; + public static final int FILE_1_REF = 1_111; + public static final int FILE_2_REF = 1_112; + public static final int FILE_3_REF = 1_121; + public static final int DIR_1_REF = 3; + public static final int DIR_2_REF = 4; + public static final long NOW = 1_800_000_000_000L; @Rule public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); @@ -60,34 +72,63 @@ public class LastCommitVisitorTest { public BatchReportReaderRule reportReader = new BatchReportReaderRule(); @Rule - public MetricRepositoryRule metricRepository = new MetricRepositoryRule().add(CoreMetrics.LAST_COMMIT_DATE); + public MetricRepositoryRule metricRepository = new MetricRepositoryRule() + .add(CoreMetrics.LAST_COMMIT_DATE) + .add(CoreMetrics.DAYS_SINCE_LAST_COMMIT); @Rule public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository); + System2 system2 = mock(System2.class); + + @Before + public void setUp() { + when(system2.now()).thenReturn(NOW); + } + @Test - public void aggregate_date_of_last_commit_to_directories_and_project() throws Exception { + public void aggregate_date_of_last_commit_to_directories_and_project() { + final long FILE_1_DATE = 1_100_000_000_000L; + // FILE_2 is the most recent file in DIR_1 + final long FILE_2_DATE = 1_200_000_000_000L; + // FILE_3 is the most recent file in the project + final long FILE_3_DATE = 1_300_000_000_000L; + // simulate the output of visitFile() - LastCommitVisitor visitor = new LastCommitVisitor(reportReader, metricRepository, measureRepository) { + LastCommitVisitor visitor = new LastCommitVisitor(reportReader, metricRepository, measureRepository, system2) { @Override public void visitFile(Component file, Path path) { - path.parent().addDate(file.getReportAttributes().getRef() * 1_000_000_000L); + long fileDate; + switch (file.getReportAttributes().getRef()) { + case FILE_1_REF: + fileDate = FILE_1_DATE; + break; + case FILE_2_REF: + fileDate = FILE_2_DATE; + break; + case FILE_3_REF: + fileDate = FILE_3_DATE; + break; + default: + throw new IllegalArgumentException(); + } + path.parent().addDate(fileDate); } }; // project with 1 module, 2 directories and 3 files - ReportComponent project = ReportComponent.builder(PROJECT, 1) + ReportComponent project = ReportComponent.builder(PROJECT, PROJECT_REF) .addChildren( - ReportComponent.builder(MODULE, 11) + ReportComponent.builder(MODULE, MODULE_REF) .addChildren( - ReportComponent.builder(DIRECTORY, 111) + ReportComponent.builder(DIRECTORY, DIR_1_REF) .addChildren( - createFileComponent(1111), - createFileComponent(1112)) + createFileComponent(FILE_1_REF), + createFileComponent(FILE_2_REF)) .build(), - ReportComponent.builder(DIRECTORY, 112) + ReportComponent.builder(DIRECTORY, DIR_2_REF) .addChildren( - createFileComponent(1121)) + createFileComponent(FILE_3_REF)) .build()) .build()) .build(); @@ -96,62 +137,79 @@ public class LastCommitVisitorTest { VisitorsCrawler underTest = new VisitorsCrawler(Lists.newArrayList(visitor)); underTest.visit(project); - // directories - assertDate(111, 1_112_000_000_000L); - assertDate(112, 1_121_000_000_000L); + assertDate(DIR_1_REF, FILE_2_DATE); + assertDaysSinceLastCommit(DIR_1_REF, 6944 /* number of days between FILE_2_DATE and now */); + assertDate(DIR_2_REF, FILE_3_DATE); + assertDaysSinceLastCommit(DIR_2_REF, 5787 /* number of days between FILE_3_DATE and now */); // module = most recent commit date of directories - assertDate(11, 1_121_000_000_000L); + assertDate(MODULE_REF, FILE_3_DATE); + assertDaysSinceLastCommit(MODULE_REF, 5787 /* number of days between FILE_3_DATE and now */); // project - assertDate(1, 1_121_000_000_000L); + assertDate(PROJECT_REF, FILE_3_DATE); + assertDaysSinceLastCommit(PROJECT_REF, 5787 /* number of days between FILE_3_DATE and now */); } @Test - public void aggregate_date_of_last_commit_to_views() throws Exception { + public void aggregate_date_of_last_commit_to_views() { + final int VIEW_REF = 1; + final int SUBVIEW_1_REF = 2; + final int SUBVIEW_2_REF = 3; + final int SUBVIEW_3_REF = 4; + final int PROJECT_1_REF = 5; + final int PROJECT_2_REF = 6; + final int PROJECT_3_REF = 7; + final long PROJECT_1_DATE = 1_500_000_000_000L; + // the second project has the most recent commit date + final long PROJECT_2_DATE = 1_700_000_000_000L; + final long PROJECT_3_DATE = 1_600_000_000_000L; // view with 3 nested sub-views and 3 projects - ViewsComponent view = ViewsComponent.builder(VIEW, 1) + ViewsComponent view = ViewsComponent.builder(VIEW, VIEW_REF) .addChildren( - builder(SUBVIEW, 11) + builder(SUBVIEW, SUBVIEW_1_REF) .addChildren( - builder(SUBVIEW, 111) + builder(SUBVIEW, SUBVIEW_2_REF) .addChildren( - builder(PROJECT_VIEW, 1111).build(), - builder(PROJECT_VIEW, 1112).build()) + builder(PROJECT_VIEW, PROJECT_1_REF).build(), + builder(PROJECT_VIEW, PROJECT_2_REF).build()) .build(), - builder(SUBVIEW, 112) + builder(SUBVIEW, SUBVIEW_3_REF) .addChildren( - builder(PROJECT_VIEW, 1121).build()) + builder(PROJECT_VIEW, PROJECT_3_REF).build()) .build()) .build()) .build(); treeRootHolder.setRoot(view); - // the second project has the most recent commit date - measureRepository.addRawMeasure(1111, CoreMetrics.LAST_COMMIT_DATE_KEY, newMeasureBuilder().create(1_500_000_000_000L)); - measureRepository.addRawMeasure(1112, CoreMetrics.LAST_COMMIT_DATE_KEY, newMeasureBuilder().create(1_700_000_000_000L)); - measureRepository.addRawMeasure(1121, CoreMetrics.LAST_COMMIT_DATE_KEY, newMeasureBuilder().create(1_600_000_000_000L)); + measureRepository.addRawMeasure(PROJECT_1_REF, LAST_COMMIT_DATE_KEY, newMeasureBuilder().create(PROJECT_1_DATE)); + measureRepository.addRawMeasure(PROJECT_2_REF, LAST_COMMIT_DATE_KEY, newMeasureBuilder().create(PROJECT_2_DATE)); + measureRepository.addRawMeasure(PROJECT_3_REF, LAST_COMMIT_DATE_KEY, newMeasureBuilder().create(PROJECT_3_DATE)); - VisitorsCrawler underTest = new VisitorsCrawler(Lists.newArrayList(new LastCommitVisitor(reportReader, metricRepository, measureRepository))); + VisitorsCrawler underTest = new VisitorsCrawler(Lists.newArrayList(new LastCommitVisitor(reportReader, metricRepository, measureRepository, system2))); underTest.visit(view); // second level of sub-views - assertDate(111, 1_700_000_000_000L); - assertDate(112, 1_600_000_000_000L); + assertDate(SUBVIEW_2_REF, PROJECT_2_DATE); + assertDaysSinceLastCommit(SUBVIEW_2_REF, 1157 /* nb of days between PROJECT_2_DATE and NOW */); + assertDate(SUBVIEW_3_REF, PROJECT_3_DATE); + assertDaysSinceLastCommit(SUBVIEW_3_REF, 2314 /* nb of days between PROJECT_3_DATE and NOW */); // first level of sub-views - assertDate(11, 1_700_000_000_000L); + assertDate(SUBVIEW_1_REF, PROJECT_2_DATE); + assertDaysSinceLastCommit(SUBVIEW_1_REF, 1157 /* nb of days between PROJECT_2_DATE and NOW */); // view - assertDate(1, 1_700_000_000_000L); + assertDate(VIEW_REF, PROJECT_2_DATE); + assertDaysSinceLastCommit(VIEW_REF, 1157 /* nb of days between PROJECT_2_DATE and NOW */); } @Test public void compute_date_of_file_from_blame_info_of_report() throws Exception { - VisitorsCrawler underTest = new VisitorsCrawler(Lists.newArrayList(new LastCommitVisitor(reportReader, metricRepository, measureRepository))); + VisitorsCrawler underTest = new VisitorsCrawler(Lists.newArrayList(new LastCommitVisitor(reportReader, metricRepository, measureRepository, system2))); BatchReport.Changesets changesets = BatchReport.Changesets.newBuilder() - .setComponentRef(FILE_REF) + .setComponentRef(FILE_1_REF) .addChangeset(BatchReport.Changesets.Changeset.newBuilder() .setAuthor("john") .setDate(1_500_000_000_000L) @@ -171,12 +229,13 @@ public class LastCommitVisitorTest { .addChangesetIndexByLine(0) .build(); reportReader.putChangesets(changesets); - ReportComponent file = createFileComponent(FILE_REF); + ReportComponent file = createFileComponent(FILE_1_REF); treeRootHolder.setRoot(file); underTest.visit(file); - assertDate(FILE_REF, 1_600_000_000_000L); + assertDate(FILE_1_REF, 1_600_000_000_000L); + assertDaysSinceLastCommit(FILE_1_REF, 2314); } private void assertDate(int componentRef, long expectedDate) { @@ -185,6 +244,12 @@ public class LastCommitVisitorTest { assertThat(measure.get().getLongValue()).isEqualTo(expectedDate); } + private void assertDaysSinceLastCommit(int componentRef, int numberOfDays) { + Optional measure = measureRepository.getAddedRawMeasure(componentRef, DAYS_SINCE_LAST_COMMIT_KEY); + assertThat(measure.isPresent()).isTrue(); + assertThat(measure.get().getIntValue()).isEqualTo(numberOfDays); + } + /** * When the file was not changed since previous analysis, than the report may not contain * the SCM blame information. In this case the date of last commit is loaded @@ -192,25 +257,25 @@ public class LastCommitVisitorTest { */ @Test public void reuse_date_of_previous_analysis_if_blame_info_is_not_in_report() throws Exception { - VisitorsCrawler underTest = new VisitorsCrawler(Lists.newArrayList(new LastCommitVisitor(reportReader, metricRepository, measureRepository))); - ReportComponent file = createFileComponent(FILE_REF); + VisitorsCrawler underTest = new VisitorsCrawler(Lists.newArrayList(new LastCommitVisitor(reportReader, metricRepository, measureRepository, system2))); + ReportComponent file = createFileComponent(FILE_1_REF); treeRootHolder.setRoot(file); - measureRepository.addBaseMeasure(FILE_REF, LAST_COMMIT_DATE_KEY, newMeasureBuilder().create(1_500_000_000L)); + measureRepository.addBaseMeasure(FILE_1_REF, LAST_COMMIT_DATE_KEY, newMeasureBuilder().create(1_500_000_000L)); underTest.visit(file); - assertDate(FILE_REF, 1_500_000_000L); + assertDate(FILE_1_REF, 1_500_000_000L); } @Test public void date_is_not_computed_on_file_if_blame_is_not_in_report_nor_in_previous_analysis() throws Exception { - VisitorsCrawler underTest = new VisitorsCrawler(Lists.newArrayList(new LastCommitVisitor(reportReader, metricRepository, measureRepository))); - ReportComponent file = createFileComponent(FILE_REF); + VisitorsCrawler underTest = new VisitorsCrawler(Lists.newArrayList(new LastCommitVisitor(reportReader, metricRepository, measureRepository, system2))); + ReportComponent file = createFileComponent(FILE_1_REF); treeRootHolder.setRoot(file); underTest.visit(file); - Optional measure = measureRepository.getAddedRawMeasure(FILE_REF, LAST_COMMIT_DATE_KEY); + Optional measure = measureRepository.getAddedRawMeasure(FILE_1_REF, LAST_COMMIT_DATE_KEY); assertThat(measure.isPresent()).isFalse(); } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java index 797c48bf78c..f214f49bb41 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java @@ -2224,7 +2224,7 @@ public final class CoreMetrics { /** * @since 5.2 – was computed in the dev cockpit plugin previously */ - public static final Metric DAYS_SINCE_LAST_COMMIT = new Metric.Builder(DAYS_SINCE_LAST_COMMIT_KEY, "Days since last commit", Metric.ValueType.INT) + public static final Metric DAYS_SINCE_LAST_COMMIT = new Metric.Builder(DAYS_SINCE_LAST_COMMIT_KEY, "Days since last commit", Metric.ValueType.INT) .setDomain(CoreMetrics.DOMAIN_SCM) .create(); -- 2.39.5