summaryrefslogtreecommitdiffstats
path: root/plugins
diff options
context:
space:
mode:
authorsimonbrandhof <simon.brandhof@gmail.com>2010-11-29 09:56:20 +0000
committersimonbrandhof <simon.brandhof@gmail.com>2010-11-29 09:56:20 +0000
commit138f4ebc4f2f7f30339153879c11bb51b33034bd (patch)
tree09761d941013efd2ac2a7cfab56621bd28bd2bdb /plugins
parentb0b8d34052b9e91b172ee38103c678524dfc93f3 (diff)
downloadsonarqube-138f4ebc4f2f7f30339153879c11bb51b33034bd.tar.gz
sonarqube-138f4ebc4f2f7f30339153879c11bb51b33034bd.zip
SONAR-249 add the decorator to calculate differential values of measures.
Diffstat (limited to 'plugins')
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java15
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CheckAlertThresholds.java19
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/GenerateAlertEvents.java1
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/DifferentialValueDecorator.java200
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/PeriodLocator.java74
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/TendencyAnalyser.java (renamed from plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/TendencyAnalyser.java)2
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/TendencyDecorator.java (renamed from plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/TendencyDecorator.java)31
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/DifferentialValueDecoratorTest.java78
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/PeriodLocatorTest.java99
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/TendencyAnalyserTest.java (renamed from plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/TendencyAnalyserTest.java)7
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/TendencyDecoratorTest.java (renamed from plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/TendencyDecoratorTest.java)10
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/DifferentialValueDecoratorTest/shared.xml85
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/PeriodLocatorTest/shouldLoadSnapshotsFromDatabase.xml76
13 files changed, 663 insertions, 34 deletions
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
index b18eeff5366..ae915535904 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
@@ -19,7 +19,11 @@
*/
package org.sonar.plugins.core;
-import org.sonar.api.*;
+import com.google.common.collect.Lists;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.Plugin;
+import org.sonar.api.Properties;
+import org.sonar.api.Property;
import org.sonar.api.checks.NoSonarFilter;
import org.sonar.api.resources.Java;
import org.sonar.plugins.core.batch.ExcludedResourceFilter;
@@ -36,11 +40,13 @@ import org.sonar.plugins.core.metrics.UserManagedMetrics;
import org.sonar.plugins.core.security.ApplyProjectRolesDecorator;
import org.sonar.plugins.core.sensors.*;
import org.sonar.plugins.core.testdetailsviewer.TestsViewerDefinition;
+import org.sonar.plugins.core.timemachine.DifferentialValueDecorator;
+import org.sonar.plugins.core.timemachine.PeriodLocator;
+import org.sonar.plugins.core.timemachine.TendencyDecorator;
import org.sonar.plugins.core.ui.pageselector.GwtPageSelector;
import org.sonar.plugins.core.violationsviewer.ViolationsViewerDefinition;
import org.sonar.plugins.core.widgets.*;
-import java.util.ArrayList;
import java.util.List;
@Properties({
@@ -125,7 +131,7 @@ public class CorePlugin implements Plugin {
}
public List getExtensions() {
- List extensions = new ArrayList();
+ List extensions = Lists.newLinkedList();
// languages
extensions.add(Java.class);
@@ -186,6 +192,9 @@ public class CorePlugin implements Plugin {
extensions.add(DirectoriesDecorator.class);
extensions.add(FilesDecorator.class);
+ extensions.add(PeriodLocator.class);
+ extensions.add(DifferentialValueDecorator.class);
+
return extensions;
}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CheckAlertThresholds.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CheckAlertThresholds.java
index 8e55fa3890d..5b3be2accff 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CheckAlertThresholds.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CheckAlertThresholds.java
@@ -19,11 +19,9 @@
*/
package org.sonar.plugins.core.sensors;
+import com.google.common.collect.Lists;
import org.apache.commons.lang.StringUtils;
-import org.sonar.api.batch.Decorator;
-import org.sonar.api.batch.DecoratorContext;
-import org.sonar.api.batch.DependedUpon;
-import org.sonar.api.batch.Phase;
+import org.sonar.api.batch.*;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Measure;
import org.sonar.api.measures.Metric;
@@ -33,10 +31,8 @@ import org.sonar.api.resources.Project;
import org.sonar.api.resources.Resource;
import org.sonar.api.resources.ResourceUtils;
-import java.util.ArrayList;
import java.util.List;
-@Phase(name = Phase.Name.POST)
public class CheckAlertThresholds implements Decorator {
private final RulesProfile profile;
@@ -50,6 +46,15 @@ public class CheckAlertThresholds implements Decorator {
return CoreMetrics.ALERT_STATUS;
}
+ @DependsUpon
+ public List<Metric> dependsUponMetrics() {
+ List<Metric> metrics = Lists.newLinkedList();
+ for (Alert alert : profile.getAlerts()) {
+ metrics.add(alert.getMetric());
+ }
+ return metrics;
+ }
+
public boolean shouldExecuteOnProject(Project project) {
return profile != null
@@ -66,7 +71,7 @@ public class CheckAlertThresholds implements Decorator {
private void decorateResource(DecoratorContext context) {
Metric.Level globalLevel = Metric.Level.OK;
- List<String> labels = new ArrayList<String>();
+ List<String> labels = Lists.newArrayList();
for (final Alert alert : profile.getAlerts()) {
Measure measure = context.getMeasure(alert.getMetric());
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/GenerateAlertEvents.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/GenerateAlertEvents.java
index f8af4708d3f..62b8882fd2d 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/GenerateAlertEvents.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/GenerateAlertEvents.java
@@ -30,7 +30,6 @@ import org.sonar.api.resources.ResourceUtils;
import java.util.List;
-@Phase(name = Phase.Name.POST)
public class GenerateAlertEvents implements Decorator {
private final RulesProfile profile;
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/DifferentialValueDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/DifferentialValueDecorator.java
new file mode 100644
index 00000000000..57c9c5619ed
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/DifferentialValueDecorator.java
@@ -0,0 +1,200 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.core.timemachine;
+
+import com.google.common.collect.Maps;
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.lang.ObjectUtils;
+import org.sonar.api.batch.*;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.database.model.MeasureModel;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.measures.*;
+import org.sonar.api.qualitymodel.Characteristic;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RulePriority;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+@DependedUpon(DecoratorBarriers.END_OF_TIME_MACHINE)
+public class DifferentialValueDecorator implements Decorator {
+
+ private Snapshot[] projectTargetSnapshots;
+ private Map<Integer, Metric> metricByIds;
+ private DatabaseSession session;
+
+ public DifferentialValueDecorator(DatabaseSession session, PeriodLocator periodLocator, Configuration configuration, MetricFinder metricFinder) {
+ this.session = session;
+ Snapshot snapshot = periodLocator.locate(5);
+ projectTargetSnapshots = new Snapshot[] {snapshot};
+
+ this.metricByIds = Maps.newHashMap();
+ for (Metric metric : metricFinder.findAll()) {
+ if (metric.isNumericType()) {
+ metricByIds.put(metric.getId(), metric);
+ }
+ }
+ }
+
+ /**
+ * only for unit tests
+ */
+ DifferentialValueDecorator(DatabaseSession session, Snapshot[] projectTargetSnapshots, Metric[] metrics) {
+ this.session = session;
+ this.projectTargetSnapshots = projectTargetSnapshots;
+ this.metricByIds = Maps.newHashMap();
+ for (Metric metric : metrics) {
+ metricByIds.put(metric.getId(), metric);
+ }
+ }
+
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+
+ @DependsUpon
+ public Collection<Metric> dependsUponMetrics() {
+ return metricByIds.values();
+ }
+
+ public void decorate(Resource resource, DecoratorContext context) {
+ for (int index = 0; index < projectTargetSnapshots.length; index++) {
+ Snapshot projectTargetSnapshot = projectTargetSnapshots[index];
+ if (projectTargetSnapshot != null) {
+ // search past measures
+ List<MeasureModel> pastMeasures = selectPastMeasures(resource.getId(), projectTargetSnapshot);
+
+ Map<MeasureKey, MeasureModel> pastMeasuresByKey = Maps.newHashMap();
+ for (MeasureModel pastMeasure : pastMeasures) {
+ pastMeasuresByKey.put(new MeasureKey(pastMeasure), pastMeasure);
+ }
+
+ // for each measure, search equivalent past measure
+ for (Measure measure : context.getMeasures(MeasuresFilters.all())) {
+ // compare with past measure
+ MeasureModel pastMeasure = pastMeasuresByKey.get(new MeasureKey(measure));
+ if (calculateDiffValue(measure, pastMeasure, index)) {
+ context.saveMeasure(measure);
+ }
+ }
+ }
+ }
+ }
+
+ private boolean calculateDiffValue(Measure measure, MeasureModel pastMeasure, int index) {
+ boolean updated = false;
+ if (pastMeasure != null && pastMeasure.getValue() != null && measure.getValue() != null) {
+ double diff = (measure.getValue().doubleValue() - pastMeasure.getValue().doubleValue());
+ updated = true;
+ switch (index) {
+ case 0:
+ measure.setDiffValue1(diff);
+ break;
+ case 1:
+ measure.setDiffValue2(diff);
+ break;
+ case 2:
+ measure.setDiffValue3(diff);
+ break;
+ default:
+ updated = false;
+ }
+ }
+ return updated;
+ }
+
+ List<MeasureModel> selectPastMeasures(int resourceId, Snapshot projectTargetSnapshot) {
+ // improvements : keep query in cache ? select only some columns ?
+ // TODO support measure on rules and characteristics
+ String hql = "select m from " + MeasureModel.class.getSimpleName() + " m, " + Snapshot.class.getSimpleName() + " s " +
+ "where m.snapshotId=s.id and m.metricId in (:metricIds) and m.rule=null and m.rulePriority=null and m.rulesCategoryId=null and m.characteristic=null "
+ + "and (s.rootId=:rootSnapshotId or s.id=:rootSnapshotId) and s.resourceId=:resourceId and s.status=:status";
+ return session.createQuery(hql)
+ .setParameter("metricIds", metricByIds.keySet())
+ .setParameter("rootSnapshotId", ObjectUtils.defaultIfNull(projectTargetSnapshot.getRootId(), projectTargetSnapshot.getId()))
+ .setParameter("resourceId", resourceId)
+ .setParameter("status", Snapshot.STATUS_PROCESSED)
+ .getResultList();
+ }
+
+ @Override
+ public String toString() {
+ return getClass().toString();
+ }
+
+ static class MeasureKey {
+ Integer metricId;
+ Rule rule;
+ Integer categoryId;
+ RulePriority priority;
+ Characteristic characteristic;
+
+ MeasureKey(MeasureModel model) {
+ this.metricId = model.getMetricId();
+ rule = model.getRule();
+ categoryId = model.getRulesCategoryId();
+ priority = model.getRulePriority();
+ characteristic = model.getCharacteristic();
+ }
+
+ MeasureKey(Measure measure) {
+ metricId = measure.getMetric().getId();
+ characteristic = measure.getCharacteristic();
+ // TODO merge RuleMeasure into Measure
+ if (measure instanceof RuleMeasure) {
+ RuleMeasure rm = (RuleMeasure)measure;
+ categoryId = rm.getRuleCategory();
+ rule = rm.getRule();
+ priority = rm.getRulePriority();
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ MeasureKey that = (MeasureKey) o;
+
+ if (categoryId != null ? !categoryId.equals(that.categoryId) : that.categoryId != null) return false;
+ if (characteristic != null ? !characteristic.equals(that.characteristic) : that.characteristic != null)
+ return false;
+ if (!metricId.equals(that.metricId)) return false;
+ if (priority != that.priority) return false;
+ if (rule != null ? !rule.equals(that.rule) : that.rule != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = metricId.hashCode();
+ result = 31 * result + (rule != null ? rule.hashCode() : 0);
+ result = 31 * result + (categoryId != null ? categoryId.hashCode() : 0);
+ result = 31 * result + (priority != null ? priority.hashCode() : 0);
+ result = 31 * result + (characteristic != null ? characteristic.hashCode() : 0);
+ return result;
+ }
+ }
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/PeriodLocator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/PeriodLocator.java
new file mode 100644
index 00000000000..35e960023b1
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/PeriodLocator.java
@@ -0,0 +1,74 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.core.timemachine;
+
+import org.apache.commons.lang.time.DateUtils;
+import org.sonar.api.BatchExtension;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.database.model.Snapshot;
+
+import java.util.Date;
+import java.util.List;
+
+// TODO should implement BatchComponent
+public final class PeriodLocator implements BatchExtension {
+ private Snapshot projectSnapshot; // TODO replace by PersistenceManager
+ private DatabaseSession session;
+
+ public PeriodLocator(Snapshot projectSnapshot, DatabaseSession session) {
+ this.projectSnapshot = projectSnapshot;
+ this.session = session;
+ }
+
+ public Snapshot locate(int days) {
+ List<Snapshot> snapshots = loadSnapshotsFromDatabase();
+ return getNearestToTarget(snapshots, projectSnapshot.getCreatedAt(), days);
+ }
+
+ List<Snapshot> loadSnapshotsFromDatabase() {
+ String hql = "from " + Snapshot.class.getSimpleName() + " where resourceId=:resourceId AND status=:status order by createdAt";
+ return session.createQuery(hql)
+ .setParameter("resourceId", projectSnapshot.getResourceId())
+ .setParameter("status", Snapshot.STATUS_PROCESSED)
+ .getResultList();
+ }
+
+ static Snapshot getNearestToTarget(List<Snapshot> snapshots, Date currentDate, int distanceInDays) {
+ Date targetDate = DateUtils.addDays(currentDate, -distanceInDays);
+ return getNearestToTarget(snapshots, targetDate);
+ }
+
+ static Snapshot getNearestToTarget(List<Snapshot> snapshots, Date targetDate) {
+ long bestDistance = Long.MAX_VALUE;
+ Snapshot nearest = null;
+ for (Snapshot snapshot : snapshots) {
+ long distance = distance(snapshot.getCreatedAt(), targetDate);
+ if (distance<=bestDistance) {
+ bestDistance = distance;
+ nearest = snapshot;
+ }
+ }
+ return nearest;
+ }
+
+ static long distance(Date d1, Date d2) {
+ return Math.abs(d1.getTime() - d2.getTime());
+ }
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/TendencyAnalyser.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/TendencyAnalyser.java
index 0ffde81e912..16ccf41df5d 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/TendencyAnalyser.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/TendencyAnalyser.java
@@ -17,7 +17,7 @@
* License along with Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
-package org.sonar.plugins.core.sensors;
+package org.sonar.plugins.core.timemachine;
import java.util.List;
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/TendencyDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/TendencyDecorator.java
index 4a0c9537704..c28c1f52084 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/TendencyDecorator.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/TendencyDecorator.java
@@ -17,40 +17,45 @@
* License along with Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
-package org.sonar.plugins.core.sensors;
+package org.sonar.plugins.core.timemachine;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.lang.time.DateUtils;
import org.sonar.api.CoreProperties;
import org.sonar.api.batch.*;
import org.sonar.api.measures.Measure;
import org.sonar.api.measures.Metric;
+import org.sonar.api.measures.MetricFinder;
import org.sonar.api.resources.Project;
import org.sonar.api.resources.Resource;
import org.sonar.api.resources.ResourceUtils;
-import org.sonar.jpa.dao.MeasuresDao;
-import java.util.ArrayList;
import java.util.List;
-@Phase(name = Phase.Name.POST)
+@DependedUpon(DecoratorBarriers.END_OF_TIME_MACHINE)
public class TendencyDecorator implements Decorator {
public static final String PROP_DAYS_DESCRIPTION = "Number of days the tendency should be calculated on.";
- private MeasuresDao measuresDao;
private TimeMachine timeMachine;
private TimeMachineQuery query;
private TendencyAnalyser analyser;
private Configuration configuration;
+ private List<Metric> metrics;
- public TendencyDecorator(TimeMachine timeMachine, MeasuresDao measuresDao, Configuration configuration) {
+ public TendencyDecorator(TimeMachine timeMachine, MetricFinder metricFinder, Configuration configuration) {
this.timeMachine = timeMachine;
- this.measuresDao = measuresDao;
this.analyser = new TendencyAnalyser();
this.configuration = configuration;
+ this.metrics = Lists.newLinkedList();
+ for (Metric metric : metricFinder.findAll()) {
+ if (metric.isNumericType()) {
+ metrics.add(metric);
+ }
+ }
}
protected TendencyDecorator(TimeMachine timeMachine, TimeMachineQuery query, TendencyAnalyser analyser, Configuration configuration) {
@@ -60,16 +65,14 @@ public class TendencyDecorator implements Decorator {
this.configuration = configuration;
}
+ @DependsUpon
+ public List<Metric> dependsUponMetrics() {
+ return metrics;
+ }
+
protected TimeMachineQuery initQuery(Project project) {
int days = project.getConfiguration().getInt(CoreProperties.CORE_TENDENCY_DEPTH_PROPERTY, CoreProperties.CORE_TENDENCY_DEPTH_DEFAULT_VALUE);
- List<Metric> metrics = new ArrayList<Metric>();
- for (Metric metric : measuresDao.getMetrics()) {
- if (metric.isNumericType()) {
- metrics.add(metric);
- }
- }
-
query = new TimeMachineQuery(null) // resource is set after
.setFrom(DateUtils.addDays(project.getAnalysisDate(), -days))
.setToCurrentAnalysis(true)
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/DifferentialValueDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/DifferentialValueDecoratorTest.java
new file mode 100644
index 00000000000..bf4691d01d1
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/DifferentialValueDecoratorTest.java
@@ -0,0 +1,78 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.core.timemachine;
+
+import org.junit.Test;
+import org.sonar.api.database.model.MeasureModel;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.measures.Metric;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class DifferentialValueDecoratorTest extends AbstractDbUnitTestCase {
+
+ private static final int PROJECT_SNAPSHOT_ID = 1000;
+ private static final int PROJECT_ID = 1;
+ private static final int FILE_ID = 3;
+
+ @Test
+ public void shouldSelectPastResourceMeasures() {
+ setupData("shared");
+
+ Metric[] metrics = selectMetrics();
+ Snapshot projectSnapshot = getSession().getSingleResult(Snapshot.class, "id", PROJECT_SNAPSHOT_ID);
+
+ DifferentialValueDecorator decorator = new DifferentialValueDecorator(getSession(), new Snapshot[0], metrics);
+ List<MeasureModel> measures = decorator.selectPastMeasures(FILE_ID, projectSnapshot);
+ assertThat(measures.size(), is(2));
+
+ for (MeasureModel measure : measures) {
+ assertThat(measure.getId(), anyOf(is(5L), is(6L)));
+ assertThat(measure.getValue(), anyOf(is(5.0), is(60.0)));
+ }
+ }
+
+ @Test
+ public void shouldSelectPastProjectMeasures() {
+ setupData("shared");
+
+ Metric[] metrics = selectMetrics();
+ Snapshot projectSnapshot = getSession().getSingleResult(Snapshot.class, "id", PROJECT_SNAPSHOT_ID);
+
+ DifferentialValueDecorator decorator = new DifferentialValueDecorator(getSession(), new Snapshot[0], metrics);
+ List<MeasureModel> measures = decorator.selectPastMeasures(PROJECT_ID, projectSnapshot);
+ assertThat(measures.size(), is(2));
+
+ for (MeasureModel measure : measures) {
+ assertThat(measure.getId(), anyOf(is(1L), is(2L)));
+ assertThat(measure.getValue(), anyOf(is(60.0), is(80.0)));
+ }
+ }
+
+ private Metric[] selectMetrics() {
+ List<Metric> metrics = getSession().getResults(Metric.class);
+ return metrics.toArray(new Metric[metrics.size()]);
+ }
+}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/PeriodLocatorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/PeriodLocatorTest.java
new file mode 100644
index 00000000000..d0d99fed25f
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/PeriodLocatorTest.java
@@ -0,0 +1,99 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.core.timemachine;
+
+import com.google.common.collect.Lists;
+import org.junit.Test;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.junit.Assert.assertThat;
+import static org.junit.internal.matchers.IsCollectionContaining.hasItems;
+
+public class PeriodLocatorTest extends AbstractDbUnitTestCase {
+
+ private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+
+ @Test
+ public void shouldLoadSnapshotsFromDatabase() {
+ setupData("shouldLoadSnapshotsFromDatabase");
+
+ Snapshot projectSnapshot = getSession().getSingleResult(Snapshot.class, "id", 1009);
+ PeriodLocator locator = new PeriodLocator(projectSnapshot, getSession());
+ List<Snapshot> snapshots = locator.loadSnapshotsFromDatabase();
+ List<Integer> snapshotIds = Lists.newLinkedList();
+ for (Snapshot snapshot : snapshots) {
+ snapshotIds.add(snapshot.getId());
+ }
+ assertThat(snapshotIds.size(), is(2));
+ assertThat(snapshotIds, hasItems(1000, 1003)); // project snapshots
+ }
+
+ @Test
+ public void shouldLocateNearestSnapshotBefore() throws ParseException {
+ Date current = dateFormat.parse("2010-10-20");
+ // distance: 15 => target is 2010-10-05
+
+ List<Snapshot> snapshots = Arrays.asList(
+ newSnapshot(1, "2010-09-30"),
+ newSnapshot(2, "2010-10-03"),// -2 days
+ newSnapshot(3, "2010-10-08"),// +3 days
+ newSnapshot(4, "2010-10-12") // + 7 days
+ );
+ assertThat(PeriodLocator.getNearestToTarget(snapshots, current, 15).getId(), is(2));
+ }
+
+ @Test
+ public void shouldLocateNearestSnapshotAfter() throws ParseException {
+ Date current = dateFormat.parse("2010-10-20");
+ // distance: 15 => target is 2010-10-05
+
+ List<Snapshot> snapshots = Arrays.asList(
+ newSnapshot(1, "2010-09-30"),
+ newSnapshot(2, "2010-10-01"),// -4 days
+ newSnapshot(3, "2010-10-08"),// +3 days
+ newSnapshot(4, "2010-10-12") // + 7 days
+ );
+ assertThat(PeriodLocator.getNearestToTarget(snapshots, current, 15).getId(), is(3));
+ }
+
+ @Test
+ public void shouldReturnNullIfNoSnapshots() throws ParseException {
+ Date current = dateFormat.parse("2010-10-20");
+ List<Snapshot> snapshots = Collections.emptyList();
+ assertThat(PeriodLocator.getNearestToTarget(snapshots, current, 15), nullValue());
+ }
+
+ private Snapshot newSnapshot(int id, String date) throws ParseException {
+ Snapshot snapshot = new Snapshot();
+ snapshot.setId(id);
+ snapshot.setCreatedAt(dateFormat.parse(date));
+ return snapshot;
+ }
+}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/TendencyAnalyserTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/TendencyAnalyserTest.java
index 2bab29d3f8b..15acb78854d 100644
--- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/TendencyAnalyserTest.java
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/TendencyAnalyserTest.java
@@ -17,16 +17,17 @@
* License along with Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
-package org.sonar.plugins.core.sensors;
+package org.sonar.plugins.core.timemachine;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.junit.Assert.*;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.*;
+
public class TendencyAnalyserTest {
private TendencyAnalyser analyser = new TendencyAnalyser();
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/TendencyDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/TendencyDecoratorTest.java
index 2a2d26a13b1..e37d54ee5a8 100644
--- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/TendencyDecoratorTest.java
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/TendencyDecoratorTest.java
@@ -17,7 +17,7 @@
* License along with Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
-package org.sonar.plugins.core.sensors;
+package org.sonar.plugins.core.timemachine;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.junit.Test;
@@ -27,9 +27,9 @@ import org.sonar.api.batch.TimeMachine;
import org.sonar.api.batch.TimeMachineQuery;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.MetricFinder;
import org.sonar.api.resources.JavaPackage;
import org.sonar.api.resources.Project;
-import org.sonar.jpa.dao.MeasuresDao;
import java.text.ParseException;
import java.text.SimpleDateFormat;
@@ -49,10 +49,10 @@ public class TendencyDecoratorTest {
when(project.getAnalysisDate()).thenReturn(date("2009-12-25"));
when(project.getConfiguration()).thenReturn(new PropertiesConfiguration());
- MeasuresDao dao = mock(MeasuresDao.class);
- when(dao.getMetrics()).thenReturn(Arrays.asList(CoreMetrics.LINES, CoreMetrics.COVERAGE, CoreMetrics.COVERAGE_LINE_HITS_DATA, CoreMetrics.PROFILE));
+ MetricFinder metricFinder = mock(MetricFinder.class);
+ when(metricFinder.findAll()).thenReturn(Arrays.asList(CoreMetrics.LINES, CoreMetrics.COVERAGE, CoreMetrics.COVERAGE_LINE_HITS_DATA, CoreMetrics.PROFILE));
- TendencyDecorator decorator = new TendencyDecorator(null, dao, new PropertiesConfiguration());
+ TendencyDecorator decorator = new TendencyDecorator(null, metricFinder, new PropertiesConfiguration());
TimeMachineQuery query = decorator.initQuery(project);
assertThat(query.getMetrics().size(), is(2));
diff --git a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/DifferentialValueDecoratorTest/shared.xml b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/DifferentialValueDecoratorTest/shared.xml
new file mode 100644
index 00000000000..48fe462c4b2
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/DifferentialValueDecoratorTest/shared.xml
@@ -0,0 +1,85 @@
+<dataset>
+
+ <metrics id="1" name="ncloc" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+ <metrics id="2" name="coverage" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="0" optimized_best_value="true" best_value="100" direction="1" hidden="false"/>
+
+
+ <rules_categories id="1" name="Efficiency" description="[null]"/>
+ <rules_categories id="6" name="Usability" description="[null]"/>
+
+ <rules id="30" name="Check Header" rules_category_id="6" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck"
+ plugin_config_key="Checker/Treewalker/HeaderCheck" plugin_name="checkstyle" description="[null]" priority="4" enabled="true"
+ cardinality="SINGLE" parent_id="[null]"/>
+
+ <rules id="31" name="Equals Avoid Null" rules_category_id="6" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck"
+ plugin_config_key="Checker/TreeWalker/EqualsAvoidNull" plugin_name="checkstyle" description="[null]" priority="4" enabled="true"
+ cardinality="SINGLE" parent_id="[null]"/>
+
+ <!-- project -->
+ <projects long_name="[null]" id="1" scope="PRJ" qualifier="TRK" kee="project" name="project"
+ root_id="[null]"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]"/>
+
+ <!-- package -->
+ <projects long_name="[null]" id="2" scope="DIR" qualifier="PAC" kee="project:org.foo" name="org.foo"
+ root_id="1"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.foo.Bar" id="3" scope="FIL" qualifier="CLA" kee="project:org.foo.Bar"
+ name="Bar" root_id="[null]"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]"/>
+
+
+ <!-- snapshots -->
+ <snapshots id="1000" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ scope="PRJ" qualifier="TRK" created_at="2008-11-01 13:58:00.00" version="[null]" path=""
+ status="P" islast="false" depth="0" />
+
+ <snapshots id="1001" project_id="2" parent_snapshot_id="1000" root_project_id="1" root_snapshot_id="1000"
+ scope="DIR" qualifier="PAC" created_at="2008-11-01 13:58:00.00" version="[null]" path="1000."
+ status="P" islast="false" depth="1" />
+
+ <snapshots id="1002" project_id="3" parent_snapshot_id="1001" root_project_id="1" root_snapshot_id="1000"
+ scope="FIL" qualifier="CLA" created_at="2008-11-01 13:58:00.00" version="[null]" path="1000.1001."
+ status="P" islast="false" depth="2" />
+
+
+ <!-- project measures -->
+ <project_measures id="1" VALUE="60" METRIC_ID="1" SNAPSHOT_ID="1000" alert_text="[null]" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]" rule_priority="[null]" characteristic_id="[null]" url="[null]"
+ diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"/>
+
+ <project_measures id="2" VALUE="80" METRIC_ID="2" SNAPSHOT_ID="1000" alert_text="[null]" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]" rule_priority="[null]" characteristic_id="[null]" url="[null]"
+ diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"/>
+
+ <!-- package measures -->
+ <project_measures id="3" VALUE="20" METRIC_ID="1" SNAPSHOT_ID="1001" alert_text="[null]" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]" rule_priority="[null]" characteristic_id="[null]" url="[null]"
+ diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"/>
+
+ <project_measures id="4" VALUE="70" METRIC_ID="2" SNAPSHOT_ID="1001" alert_text="[null]" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]" rule_priority="[null]" characteristic_id="[null]" url="[null]"
+ diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"/>
+
+ <!-- file measures -->
+ <project_measures id="5" VALUE="5" METRIC_ID="1" SNAPSHOT_ID="1002" alert_text="[null]" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]" rule_priority="[null]" characteristic_id="[null]" url="[null]"
+ diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"/>
+
+ <project_measures id="6" VALUE="60" METRIC_ID="2" SNAPSHOT_ID="1002" alert_text="[null]" RULES_CATEGORY_ID="[null]"
+ RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]" rule_priority="[null]" characteristic_id="[null]" url="[null]"
+ diff_value_1="[null]" diff_value_2="[null]" diff_value_3="[null]"/>
+</dataset> \ No newline at end of file
diff --git a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/PeriodLocatorTest/shouldLoadSnapshotsFromDatabase.xml b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/PeriodLocatorTest/shouldLoadSnapshotsFromDatabase.xml
new file mode 100644
index 00000000000..6e5b5adfbfc
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/PeriodLocatorTest/shouldLoadSnapshotsFromDatabase.xml
@@ -0,0 +1,76 @@
+<dataset>
+
+ <!-- project -->
+ <projects long_name="[null]" id="1" scope="PRJ" qualifier="TRK" kee="project" name="project"
+ root_id="[null]"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]"/>
+
+ <!-- package -->
+ <projects long_name="[null]" id="2" scope="DIR" qualifier="PAC" kee="project:org.foo" name="org.foo"
+ root_id="1"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]"/>
+
+ <!-- file -->
+ <projects long_name="org.foo.Bar" id="3" scope="FIL" qualifier="CLA" kee="project:org.foo.Bar"
+ name="Bar" root_id="[null]"
+ description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]"/>
+
+
+ <!-- first analysis -->
+ <snapshots id="1000" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ scope="PRJ" qualifier="TRK" created_at="2008-11-01 13:58:00.00" version="[null]" path=""
+ status="P" islast="false" depth="0" />
+
+ <snapshots id="1001" project_id="2" parent_snapshot_id="1000" root_project_id="1" root_snapshot_id="1000"
+ scope="DIR" qualifier="PAC" created_at="2008-11-01 13:58:00.00" version="[null]" path="1000."
+ status="P" islast="false" depth="1" />
+
+ <snapshots id="1002" project_id="3" parent_snapshot_id="1001" root_project_id="1" root_snapshot_id="1000"
+ scope="FIL" qualifier="CLA" created_at="2008-11-01 13:58:00.00" version="[null]" path="1000.1001."
+ status="P" islast="false" depth="2" />
+
+
+ <!-- second analysis -->
+ <snapshots id="1003" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ scope="PRJ" qualifier="TRK" created_at="2008-11-02 13:58:00.00" version="[null]" path=""
+ status="P" islast="true" depth="0" />
+
+ <snapshots id="1004" project_id="2" parent_snapshot_id="1003" root_project_id="1" root_snapshot_id="1003"
+ scope="DIR" qualifier="PAC" created_at="2008-11-02 13:58:00.00" version="[null]" path="1003."
+ status="P" islast="true" depth="1" />
+
+ <snapshots id="1005" project_id="3" parent_snapshot_id="1004" root_project_id="1" root_snapshot_id="1003"
+ scope="FIL" qualifier="CLA" created_at="2008-11-02 13:58:00.00" version="[null]" path="1003.1004."
+ status="P" islast="true" depth="2" />
+
+
+ <!-- unprocessed analysis (to ignore) -->
+ <snapshots id="1006" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ scope="PRJ" qualifier="TRK" created_at="2008-11-03 13:58:00.00" version="[null]" path=""
+ status="U" islast="false" depth="0" />
+
+ <snapshots id="1007" project_id="2" parent_snapshot_id="1006" root_project_id="1" root_snapshot_id="1006"
+ scope="DIR" qualifier="PAC" created_at="2008-11-03 13:58:00.00" version="[null]" path="1006."
+ status="U" islast="false" depth="1" />
+
+ <snapshots id="1008" project_id="3" parent_snapshot_id="1007" root_project_id="1" root_snapshot_id="1006"
+ scope="FIL" qualifier="CLA" created_at="2008-11-03 13:58:00.00" version="[null]" path="1006.1007."
+ status="U" islast="false" depth="2" />
+
+
+ <!-- current analysis -->
+ <snapshots id="1009" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ scope="PRJ" qualifier="TRK" created_at="2008-11-04 13:58:00.00" version="[null]" path=""
+ status="U" islast="false" depth="0" />
+
+ <snapshots id="1010" project_id="2" parent_snapshot_id="1009" root_project_id="1" root_snapshot_id="1009"
+ scope="DIR" qualifier="PAC" created_at="2008-11-04 13:58:00.00" version="[null]" path="1009."
+ status="U" islast="false" depth="1" />
+
+ <snapshots id="1011" project_id="3" parent_snapshot_id="1010" root_project_id="1" root_snapshot_id="1009"
+ scope="FIL" qualifier="CLA" created_at="2008-11-04 13:58:00.00" version="[null]" path="1009.1010."
+ status="U" islast="false" depth="2" />
+</dataset> \ No newline at end of file