aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-batch/src
diff options
context:
space:
mode:
authorsimonbrandhof <simon.brandhof@gmail.com>2010-12-27 00:14:52 +0000
committersimonbrandhof <simon.brandhof@gmail.com>2010-12-27 00:14:52 +0000
commitf86a62e3c24d2e2587b0f76988fbd9220ee4931a (patch)
treec5bb432cc825819db3c7837ac3b6c9c774eafb78 /sonar-batch/src
parent871954095dc3cd929608405503d4dacc224fa6ed (diff)
downloadsonarqube-f86a62e3c24d2e2587b0f76988fbd9220ee4931a.tar.gz
sonarqube-f86a62e3c24d2e2587b0f76988fbd9220ee4931a.zip
Move timemachine components from core plugin to batch + do not depend on Snapshot in PastSnapshotLoader components + fix NPE when calculating variations on measures that are still not persisted
Diffstat (limited to 'sonar-batch/src')
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/Batch.java7
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/ProjectBatch.java4
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/components/PastMeasuresLoader.java81
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshot.java92
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinder.java128
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinderByDate.java56
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinderByDays.java56
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinderByPreviousAnalysis.java55
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinderByVersion.java54
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/components/PastViolationsLoader.java48
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/components/TimeMachineConfiguration.java72
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/index/DefaultPersistenceManager.java1
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/components/PastMeasuresLoaderTest.java96
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderByDateTest.java61
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderByDaysTest.java64
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderByPreviousAnalysisTest.java54
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderByVersionTest.java51
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderTest.java183
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/components/PastViolationsLoaderTest.java55
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/index/ViolationPersisterTest.java1
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/components/PastMeasuresLoaderTest/shared.xml85
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/components/PastSnapshotFinderByDateTest/shared.xml42
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/components/PastSnapshotFinderByDaysTest/shared.xml76
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/components/PastSnapshotFinderByPreviousAnalysisTest/shouldFindPreviousAnalysis.xml26
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/components/PastSnapshotFinderByPreviousAnalysisTest/shouldNotFindPreviousAnalysis.xml21
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/components/PastSnapshotFinderByVersionTest/shared.xml42
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/components/PastViolationsLoaderTest/shared.xml24
27 files changed, 1533 insertions, 2 deletions
diff --git a/sonar-batch/src/main/java/org/sonar/batch/Batch.java b/sonar-batch/src/main/java/org/sonar/batch/Batch.java
index 91e981e41ac..bf5b1e348bd 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/Batch.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/Batch.java
@@ -33,6 +33,7 @@ import org.sonar.batch.bootstrap.BatchPluginRepository;
import org.sonar.batch.bootstrap.BootstrapClassLoader;
import org.sonar.batch.bootstrap.ExtensionDownloader;
import org.sonar.batch.bootstrap.TempDirectories;
+import org.sonar.batch.components.*;
import org.sonar.batch.index.*;
import org.sonar.core.components.CacheMetricFinder;
import org.sonar.core.components.CacheRuleFinder;
@@ -92,6 +93,12 @@ public class Batch {
batchContainer.as(Characteristics.CACHE).addComponent(MeasuresDao.class);
batchContainer.as(Characteristics.CACHE).addComponent(CacheRuleFinder.class);
batchContainer.as(Characteristics.CACHE).addComponent(CacheMetricFinder.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(PastSnapshotFinderByDate.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(PastSnapshotFinderByDays.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(PastSnapshotFinderByPreviousAnalysis.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(PastSnapshotFinderByVersion.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(PastMeasuresLoader.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(PastSnapshotFinder.class);
batchContainer.start();
ProjectTree projectTree = batchContainer.getComponent(ProjectTree.class);
diff --git a/sonar-batch/src/main/java/org/sonar/batch/ProjectBatch.java b/sonar-batch/src/main/java/org/sonar/batch/ProjectBatch.java
index afccb16fdda..6bc1ac2862c 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/ProjectBatch.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/ProjectBatch.java
@@ -36,6 +36,8 @@ import org.sonar.api.rules.DefaultRulesManager;
import org.sonar.api.utils.IocContainer;
import org.sonar.api.utils.SonarException;
import org.sonar.batch.bootstrap.BatchPluginRepository;
+import org.sonar.batch.components.PastViolationsLoader;
+import org.sonar.batch.components.TimeMachineConfiguration;
import org.sonar.batch.index.DefaultIndex;
import org.sonar.batch.index.DefaultResourcePersister;
import org.sonar.batch.phases.Phases;
@@ -93,6 +95,8 @@ public class ProjectBatch {
batchContainer.as(Characteristics.CACHE).addComponent(ViolationFilters.class);
batchContainer.as(Characteristics.CACHE).addComponent(ResourceFilters.class);
batchContainer.as(Characteristics.CACHE).addComponent(DefaultModelFinder.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(TimeMachineConfiguration.class);
+ batchContainer.as(Characteristics.CACHE).addComponent(PastViolationsLoader.class);
batchContainer.addAdapter(new ProfileProvider());
batchContainer.addAdapter(new CheckProfileProvider());
loadCoreComponents(batchContainer);
diff --git a/sonar-batch/src/main/java/org/sonar/batch/components/PastMeasuresLoader.java b/sonar-batch/src/main/java/org/sonar/batch/components/PastMeasuresLoader.java
new file mode 100644
index 00000000000..ee3ad649957
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/components/PastMeasuresLoader.java
@@ -0,0 +1,81 @@
+/*
+ * 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.batch.components;
+
+import com.google.common.collect.Maps;
+import org.apache.commons.lang.ObjectUtils;
+import org.sonar.api.BatchExtension;
+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.Metric;
+import org.sonar.api.measures.MetricFinder;
+import org.sonar.api.resources.Resource;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+public class PastMeasuresLoader implements BatchExtension {
+
+ private Map<Integer, Metric> metricByIds;
+ private DatabaseSession session;
+
+ public PastMeasuresLoader(DatabaseSession session, MetricFinder metricFinder) {
+ this(session, metricFinder.findAll());
+ }
+
+ PastMeasuresLoader(DatabaseSession session, Collection<Metric> metrics) {
+ this.session = session;
+ this.metricByIds = Maps.newHashMap();
+ for (Metric metric : metrics) {
+ if (metric.isNumericType()) {
+ metricByIds.put(metric.getId(), metric);
+ }
+ }
+ }
+
+ public Collection<Metric> getMetrics() {
+ return metricByIds.values();
+ }
+
+ public List<MeasureModel> getPastMeasures(Resource resource, PastSnapshot projectPastSnapshot) {
+ return getPastMeasures(resource, projectPastSnapshot.getProjectSnapshot());
+ }
+
+ public List<MeasureModel> getPastMeasures(Resource resource, Snapshot projectSnapshot) {
+ // assume that the resource has already been saved
+ return getPastMeasures(resource.getId(), projectSnapshot);
+ }
+
+ public List<MeasureModel> getPastMeasures(int resourceId, Snapshot projectSnapshot) {
+ // TODO improvement : select only some columns
+ // TODO support measure on 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.ruleId=null and m.rulePriority=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(projectSnapshot.getRootId(), projectSnapshot.getId()))
+ .setParameter("resourceId", resourceId)
+ .setParameter("status", Snapshot.STATUS_PROCESSED)
+ .getResultList();
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshot.java b/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshot.java
new file mode 100644
index 00000000000..b55ad44d661
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshot.java
@@ -0,0 +1,92 @@
+/*
+ * 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.batch.components;
+
+import org.apache.commons.lang.builder.ReflectionToStringBuilder;
+import org.sonar.api.database.model.Snapshot;
+
+import java.util.Date;
+
+public class PastSnapshot {
+
+ private int index;
+ private String mode, modeParameter;
+ private Snapshot projectSnapshot;
+ private Date targetDate;
+
+ public PastSnapshot(String mode, Date targetDate, Snapshot projectSnapshot) {
+ this.mode = mode;
+ this.targetDate = targetDate;
+ this.projectSnapshot = projectSnapshot;
+ }
+
+ public PastSnapshot(String mode, Snapshot projectSnapshot) {
+ this.mode = mode;
+ this.projectSnapshot = projectSnapshot;
+ }
+
+ public PastSnapshot setIndex(int index) {
+ this.index = index;
+ return this;
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ public Snapshot getProjectSnapshot() {
+ return projectSnapshot;
+ }
+
+ public Date getDate() {
+ return projectSnapshot.getCreatedAt();
+ }
+
+ public String getMode() {
+ return mode;
+ }
+
+ public String getModeParameter() {
+ return modeParameter;
+ }
+
+ public PastSnapshot setModeParameter(String s) {
+ this.modeParameter = s;
+ return this;
+ }
+
+ public Integer getProjectSnapshotId() {
+ return (projectSnapshot!=null ? projectSnapshot.getId() : null);
+ }
+
+ public Date getTargetDate() {
+ return targetDate;
+ }
+
+ public PastSnapshot setTargetDate(Date d) {
+ this.targetDate = d;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return ReflectionToStringBuilder.toString(this);
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinder.java b/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinder.java
new file mode 100644
index 00000000000..00f112a1b56
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinder.java
@@ -0,0 +1,128 @@
+/*
+ * 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.batch.components;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.BatchExtension;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.utils.Logs;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class PastSnapshotFinder implements BatchExtension {
+
+ /**
+ * IMPORTANT : please update default values in the ruby side too. See app/helpers/FiltersHelper.rb, method period_names()
+ */
+ private PastSnapshotFinderByDays finderByDays;
+ private PastSnapshotFinderByVersion finderByVersion;
+ private PastSnapshotFinderByDate finderByDate;
+ private PastSnapshotFinderByPreviousAnalysis finderByPreviousAnalysis;
+
+ public PastSnapshotFinder(PastSnapshotFinderByDays finderByDays, PastSnapshotFinderByVersion finderByVersion,
+ PastSnapshotFinderByDate finderByDate, PastSnapshotFinderByPreviousAnalysis finderByPreviousAnalysis) {
+ this.finderByDays = finderByDays;
+ this.finderByVersion = finderByVersion;
+ this.finderByDate = finderByDate;
+ this.finderByPreviousAnalysis = finderByPreviousAnalysis;
+ }
+
+ public PastSnapshot find(Snapshot projectSnapshot, Configuration conf, int index) {
+ String propertyValue = getPropertyValue(conf, index);
+ PastSnapshot pastSnapshot = find(projectSnapshot, index, propertyValue);
+ if (pastSnapshot==null && StringUtils.isNotBlank(propertyValue)) {
+ Logs.INFO.warn("The property " + CoreProperties.TIMEMACHINE_PERIOD_PREFIX + index + " has an unvalid value: " + propertyValue);
+ }
+ return pastSnapshot;
+ }
+
+ static String getPropertyValue(Configuration conf, int index) {
+ String defaultValue = null;
+ switch (index) {
+ case 1: defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_1; break;
+ case 2: defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_2; break;
+ case 3: defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_3; break;
+ case 4: defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_4; break;
+ case 5: defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_5; break;
+ }
+ return conf.getString(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + index, defaultValue);
+ }
+
+ public PastSnapshot find(Snapshot projectSnapshot, int index, String property) {
+ if (StringUtils.isBlank(property)) {
+ return null;
+ }
+
+ PastSnapshot result = findByDays(projectSnapshot, property);
+ if (result == null) {
+ result = findByDate(projectSnapshot, property);
+ if (result == null) {
+ result = findByPreviousAnalysis(projectSnapshot, property);
+ if (result == null) {
+ result = findByVersion(projectSnapshot, property);
+ }
+ }
+ }
+
+ if (result != null) {
+ result.setIndex(index);
+ }
+
+ return result;
+ }
+
+ private PastSnapshot findByPreviousAnalysis(Snapshot projectSnapshot, String property) {
+ PastSnapshot pastSnapshot = null;
+ if (StringUtils.equals(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS, property)) {
+ pastSnapshot = finderByPreviousAnalysis.findByPreviousAnalysis(projectSnapshot);
+ }
+ return pastSnapshot;
+ }
+
+ private PastSnapshot findByDate(Snapshot projectSnapshot, String property) {
+ SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+ try {
+ Date date = format.parse(property);
+ return finderByDate.findByDate(projectSnapshot, date);
+
+ } catch (ParseException e) {
+ return null;
+ }
+ }
+
+ private PastSnapshot findByVersion(Snapshot projectSnapshot, String property) {
+ return finderByVersion.findByVersion(projectSnapshot, property);
+ }
+
+ private PastSnapshot findByDays(Snapshot projectSnapshot, String property) {
+ try {
+ int days = Integer.parseInt(property);
+ return finderByDays.findFromDays(projectSnapshot, days);
+
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinderByDate.java b/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinderByDate.java
new file mode 100644
index 00000000000..75ef31b9084
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinderByDate.java
@@ -0,0 +1,56 @@
+/*
+ * 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.batch.components;
+
+import org.sonar.api.BatchExtension;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.database.model.Snapshot;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+
+public class PastSnapshotFinderByDate implements BatchExtension {
+
+ public static final String MODE = "date";
+
+ private DatabaseSession session;
+
+ public PastSnapshotFinderByDate(DatabaseSession session) {
+ this.session = session;
+ }
+
+ PastSnapshot findByDate(Snapshot projectSnapshot, Date date) {
+ String hql = "from " + Snapshot.class.getSimpleName() + " where createdAt>=:date AND resourceId=:resourceId AND status=:status order by createdAt asc";
+ List<Snapshot> snapshots = session.createQuery(hql)
+ .setParameter("date", date)
+ .setParameter("resourceId", projectSnapshot.getResourceId())
+ .setParameter("status", Snapshot.STATUS_PROCESSED)
+ .setMaxResults(1)
+ .getResultList();
+ if (snapshots.isEmpty()) {
+ return null;
+ }
+
+ SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+ return new PastSnapshot(MODE, date, snapshots.get(0)).setModeParameter(format.format(date));
+ }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinderByDays.java b/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinderByDays.java
new file mode 100644
index 00000000000..6894a802ec1
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinderByDays.java
@@ -0,0 +1,56 @@
+/*
+ * 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.batch.components;
+
+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;
+
+public class PastSnapshotFinderByDays implements BatchExtension {
+
+ public static final String MODE = "days";
+
+ private DatabaseSession session;
+
+ public PastSnapshotFinderByDays(DatabaseSession session) {
+ this.session = session;
+ }
+
+ PastSnapshot findFromDays(Snapshot projectSnapshot, int days) {
+ Date targetDate = DateUtils.addDays(projectSnapshot.getCreatedAt(), -days);
+ String hql = "from " + Snapshot.class.getSimpleName() + " where resourceId=:resourceId AND status=:status AND createdAt>=:from AND createdAt<:to order by createdAt asc";
+ List<Snapshot> snapshots = session.createQuery(hql)
+ .setParameter("from", targetDate)
+ .setParameter("to", projectSnapshot.getCreatedAt())
+ .setParameter("resourceId", projectSnapshot.getResourceId())
+ .setParameter("status", Snapshot.STATUS_PROCESSED)
+ .setMaxResults(1)
+ .getResultList();
+
+ if (snapshots.isEmpty()) {
+ return null;
+ }
+ return new PastSnapshot(MODE, targetDate, snapshots.get(0)).setModeParameter(String.valueOf(days));
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinderByPreviousAnalysis.java b/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinderByPreviousAnalysis.java
new file mode 100644
index 00000000000..55dda549bbc
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinderByPreviousAnalysis.java
@@ -0,0 +1,55 @@
+/*
+ * 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.batch.components;
+
+import org.sonar.api.BatchExtension;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.database.model.Snapshot;
+
+import java.text.SimpleDateFormat;
+import java.util.List;
+
+public class PastSnapshotFinderByPreviousAnalysis implements BatchExtension {
+
+ private DatabaseSession session;
+
+ public PastSnapshotFinderByPreviousAnalysis(DatabaseSession session) {
+ this.session = session;
+ }
+
+ PastSnapshot findByPreviousAnalysis(Snapshot projectSnapshot) {
+ String hql = "from " + Snapshot.class.getSimpleName() + " where createdAt<:date AND resourceId=:resourceId AND status=:status and last=true order by createdAt desc";
+ List<Snapshot> snapshots = session.createQuery(hql)
+ .setParameter("date", projectSnapshot.getCreatedAt())
+ .setParameter("resourceId", projectSnapshot.getResourceId())
+ .setParameter("status", Snapshot.STATUS_PROCESSED)
+ .setMaxResults(1)
+ .getResultList();
+
+ if (snapshots.isEmpty()) {
+ return null;
+ }
+ Snapshot snapshot = snapshots.get(0);
+ SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+ return new PastSnapshot(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS, snapshot.getCreatedAt(), snapshot).setModeParameter(format.format(snapshot.getCreatedAt()));
+ }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinderByVersion.java b/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinderByVersion.java
new file mode 100644
index 00000000000..cd360d2b125
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinderByVersion.java
@@ -0,0 +1,54 @@
+/*
+ * 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.batch.components;
+
+import org.sonar.api.BatchExtension;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.database.model.Snapshot;
+
+import java.util.List;
+
+public class PastSnapshotFinderByVersion implements BatchExtension {
+
+ public static final String MODE = "version";
+
+ private DatabaseSession session;
+
+ public PastSnapshotFinderByVersion(DatabaseSession session) {
+ this.session = session;
+ }
+
+ PastSnapshot findByVersion(Snapshot projectSnapshot, String version) {
+ String hql = "from " + Snapshot.class.getSimpleName() + " where version=:version AND resourceId=:resourceId AND status=:status order by createdAt desc";
+ List<Snapshot> snapshots = session.createQuery(hql)
+ .setParameter("version", version)
+ .setParameter("resourceId", projectSnapshot.getResourceId())
+ .setParameter("status", Snapshot.STATUS_PROCESSED)
+ .setMaxResults(1)
+ .getResultList();
+
+ if (snapshots.isEmpty()) {
+ return null;
+ }
+ Snapshot snapshot = snapshots.get(0);
+ return new PastSnapshot(MODE, snapshot.getCreatedAt(), snapshot).setModeParameter(snapshot.getVersion());
+ }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/components/PastViolationsLoader.java b/sonar-batch/src/main/java/org/sonar/batch/components/PastViolationsLoader.java
new file mode 100644
index 00000000000..5f238ee9aed
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/components/PastViolationsLoader.java
@@ -0,0 +1,48 @@
+package org.sonar.batch.components;
+
+import org.sonar.api.BatchExtension;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.database.model.RuleFailureModel;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.database.model.SnapshotSource;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.utils.SonarException;
+import org.sonar.batch.index.ResourcePersister;
+
+import java.util.Collections;
+import java.util.List;
+
+public class PastViolationsLoader implements BatchExtension {
+
+ private DatabaseSession session;
+ private ResourcePersister resourcePersister;
+
+ public PastViolationsLoader(DatabaseSession session, ResourcePersister resourcePersister) {
+ this.session = session;
+ this.resourcePersister = resourcePersister;
+ }
+
+ public List<RuleFailureModel> getPastViolations(Resource resource) {
+ if (resource == null) {
+ return Collections.emptyList();
+ }
+
+ Snapshot snapshot = resourcePersister.getSnapshot(resource);
+ if (snapshot == null) {
+ throw new SonarException("This resource has no snapshot ???" + resource);
+ }
+ Snapshot previousLastSnapshot = resourcePersister.getLastSnapshot(snapshot, true);
+ if (previousLastSnapshot == null) {
+ return Collections.emptyList();
+ }
+ return session.getResults(RuleFailureModel.class,
+ "snapshotId", previousLastSnapshot.getId());
+ }
+
+ public SnapshotSource getSource(Resource resource) {
+ Snapshot snapshot = resourcePersister.getSnapshot(resource);
+ return session.getSingleResult(SnapshotSource.class,
+ "snapshotId", snapshot.getId());
+ }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/components/TimeMachineConfiguration.java b/sonar-batch/src/main/java/org/sonar/batch/components/TimeMachineConfiguration.java
new file mode 100644
index 00000000000..eb1977270e7
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/components/TimeMachineConfiguration.java
@@ -0,0 +1,72 @@
+/*
+ * 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.batch.components;
+
+import com.google.common.collect.Lists;
+import org.apache.commons.configuration.Configuration;
+import org.sonar.api.BatchExtension;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.utils.Logs;
+
+import java.util.Collections;
+import java.util.List;
+
+public class TimeMachineConfiguration implements BatchExtension {
+
+ private static final int NUMBER_OF_VARIATION_SNAPSHOTS = 5;
+
+ private final Configuration configuration;
+ private List<PastSnapshot> projectPastSnapshots;
+
+ public TimeMachineConfiguration(Configuration configuration, PastSnapshotFinder pastSnapshotFinder, Snapshot projectSnapshot) {
+ this.configuration = configuration;
+ initPastSnapshots(pastSnapshotFinder, projectSnapshot);
+ }
+
+ private void initPastSnapshots(PastSnapshotFinder pastSnapshotFinder, Snapshot projectSnapshot) {
+ projectPastSnapshots = Lists.newLinkedList();
+ for (int index = 1; index <= NUMBER_OF_VARIATION_SNAPSHOTS; index++) {
+ PastSnapshot variationSnapshot = pastSnapshotFinder.find(projectSnapshot, configuration, index);
+ if (variationSnapshot != null) {
+ Logs.INFO.info("Comparison date: " + variationSnapshot.getDate());
+ projectPastSnapshots.add(variationSnapshot);
+ }
+ }
+ }
+
+ public TimeMachineConfiguration(Configuration configuration) {
+ this.configuration = configuration;
+ this.projectPastSnapshots = Collections.emptyList();
+ }
+
+
+ public boolean skipTendencies() {
+ return configuration.getBoolean(CoreProperties.SKIP_TENDENCIES_PROPERTY, CoreProperties.SKIP_TENDENCIES_DEFAULT_VALUE);
+ }
+
+ public int getTendencyPeriodInDays() {
+ return configuration.getInt(CoreProperties.CORE_TENDENCY_DEPTH_PROPERTY, CoreProperties.CORE_TENDENCY_DEPTH_DEFAULT_VALUE);
+ }
+
+ public List<PastSnapshot> getProjectPastSnapshots() {
+ return projectPastSnapshots;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultPersistenceManager.java b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultPersistenceManager.java
index c91bf3205aa..942dd3aa127 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultPersistenceManager.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultPersistenceManager.java
@@ -26,7 +26,6 @@ import org.sonar.api.measures.Measure;
import org.sonar.api.resources.Project;
import org.sonar.api.resources.ProjectLink;
import org.sonar.api.resources.Resource;
-import org.sonar.api.rules.Violation;
import java.util.List;
diff --git a/sonar-batch/src/test/java/org/sonar/batch/components/PastMeasuresLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/components/PastMeasuresLoaderTest.java
new file mode 100644
index 00000000000..1f3301424ed
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/components/PastMeasuresLoaderTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.batch.components;
+
+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.batch.components.PastMeasuresLoader;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.internal.matchers.IsCollectionContaining.hasItems;
+
+public class PastMeasuresLoaderTest 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 shouldGetPastResourceMeasures() {
+ setupData("shared");
+
+ List<Metric> metrics = selectMetrics();
+ Snapshot projectSnapshot = getSession().getSingleResult(Snapshot.class, "id", PROJECT_SNAPSHOT_ID);
+
+ PastMeasuresLoader loader = new PastMeasuresLoader(getSession(), metrics);
+ List<MeasureModel> measures = loader.getPastMeasures(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 shouldGetPastProjectMeasures() {
+ setupData("shared");
+
+ List<Metric> metrics = selectMetrics();
+ Snapshot projectSnapshot = getSession().getSingleResult(Snapshot.class, "id", PROJECT_SNAPSHOT_ID);
+
+ PastMeasuresLoader loader = new PastMeasuresLoader(getSession(), metrics);
+ List<MeasureModel> measures = loader.getPastMeasures(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)));
+ }
+ }
+
+ @Test
+ public void shouldKeepOnlyNumericalMetrics() {
+ Metric ncloc = new Metric("ncloc", Metric.ValueType.INT);
+ ncloc.setId(1);
+ Metric complexity = new Metric("complexity", Metric.ValueType.INT);
+ complexity.setId(2);
+ Metric data = new Metric("data", Metric.ValueType.DATA);
+ data.setId(3);
+ List<Metric> metrics = Arrays.asList(ncloc, complexity, data);
+
+ PastMeasuresLoader loader = new PastMeasuresLoader(getSession(), metrics);
+
+ assertThat(loader.getMetrics().size(), is(2));
+ assertThat(loader.getMetrics(), hasItems(ncloc, complexity));
+ }
+
+ private List<Metric> selectMetrics() {
+ return getSession().getResults(Metric.class);
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderByDateTest.java b/sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderByDateTest.java
new file mode 100644
index 00000000000..2013424d30c
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderByDateTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.batch.components;
+
+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.Date;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class PastSnapshotFinderByDateTest extends AbstractDbUnitTestCase {
+ public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
+
+ @Test
+ public void shouldFindDate() throws ParseException {
+ setupData("shared");
+
+ Snapshot projectSnapshot = getSession().getSingleResult(Snapshot.class, "id", 1010);
+ PastSnapshotFinderByDate finder = new PastSnapshotFinderByDate(getSession());
+
+ Date date = DATE_FORMAT.parse("2008-11-22");
+
+ PastSnapshot pastSnapshot = finder.findByDate(projectSnapshot, date);
+ assertThat(pastSnapshot.getProjectSnapshotId(), is(1006));
+ }
+
+ @Test
+ public void shouldFindNearestLaterDate() throws ParseException {
+ setupData("shared");
+
+ Snapshot projectSnapshot = getSession().getSingleResult(Snapshot.class, "id", 1010);
+ PastSnapshotFinderByDate finder = new PastSnapshotFinderByDate(getSession());
+
+ Date date = DATE_FORMAT.parse("2008-11-24");
+
+ PastSnapshot pastSnapshot = finder.findByDate(projectSnapshot, date);
+ assertThat(pastSnapshot.getProjectSnapshotId(), is(1009));
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderByDaysTest.java b/sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderByDaysTest.java
new file mode 100644
index 00000000000..6db32df71c7
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderByDaysTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.batch.components;
+
+import org.junit.Test;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+
+import java.text.ParseException;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+public class PastSnapshotFinderByDaysTest extends AbstractDbUnitTestCase {
+
+ @Test
+ public void shouldGetNextSnapshot() throws ParseException {
+ setupData("shared");
+
+ Snapshot projectSnapshot = getSession().getSingleResult(Snapshot.class, "id", 1009); // 2008-11-16
+ PastSnapshotFinderByDays finder = new PastSnapshotFinderByDays(getSession());
+
+ assertThat(finder.findFromDays(projectSnapshot, 50).getProjectSnapshotId(), is(1000));
+ }
+
+ @Test
+ public void shouldIgnoreUnprocessedSnapshots() throws ParseException {
+ setupData("shared");
+
+ Snapshot projectSnapshot = getSession().getSingleResult(Snapshot.class, "id", 1009); // 2008-11-16
+ PastSnapshotFinderByDays finder = new PastSnapshotFinderByDays(getSession());
+
+ assertThat(finder.findFromDays(projectSnapshot, 7).getProjectSnapshotId(), is(1006));
+ }
+
+ @Test
+ public void shouldNotFindSelf() throws ParseException {
+ setupData("shared");
+
+ Snapshot projectSnapshot = getSession().getSingleResult(Snapshot.class, "id", 1009); // 2008-11-16
+ PastSnapshotFinderByDays finder = new PastSnapshotFinderByDays(getSession());
+
+ assertThat(finder.findFromDays(projectSnapshot, 1), nullValue());
+ }
+
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderByPreviousAnalysisTest.java b/sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderByPreviousAnalysisTest.java
new file mode 100644
index 00000000000..b72947d275e
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderByPreviousAnalysisTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.batch.components;
+
+import org.junit.Test;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+
+import java.text.ParseException;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+
+public class PastSnapshotFinderByPreviousAnalysisTest extends AbstractDbUnitTestCase {
+
+ @Test
+ public void shouldFindPreviousAnalysis() throws ParseException {
+ setupData("shouldFindPreviousAnalysis");
+
+ Snapshot projectSnapshot = getSession().getSingleResult(Snapshot.class, "id", 1010);
+ PastSnapshotFinderByPreviousAnalysis finder = new PastSnapshotFinderByPreviousAnalysis(getSession());
+
+ PastSnapshot pastSnapshot = finder.findByPreviousAnalysis(projectSnapshot);
+ assertThat(pastSnapshot.getProjectSnapshotId(), is(1009));
+ }
+
+ @Test
+ public void shouldNotFindPreviousAnalysis() throws ParseException {
+ setupData("shouldNotFindPreviousAnalysis");
+
+ Snapshot projectSnapshot = getSession().getSingleResult(Snapshot.class, "id", 1010);
+ PastSnapshotFinderByPreviousAnalysis finder = new PastSnapshotFinderByPreviousAnalysis(getSession());
+
+ assertNull(finder.findByPreviousAnalysis(projectSnapshot));
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderByVersionTest.java b/sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderByVersionTest.java
new file mode 100644
index 00000000000..6a6f084b842
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderByVersionTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.batch.components;
+
+import org.junit.Test;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.junit.Assert.assertThat;
+
+public class PastSnapshotFinderByVersionTest extends AbstractDbUnitTestCase {
+
+ @Test
+ public void shouldFindByVersion() {
+ setupData("shared");
+
+ Snapshot currentProjectSnapshot = getSession().getSingleResult(Snapshot.class, "id", 1010);
+ PastSnapshotFinderByVersion finder = new PastSnapshotFinderByVersion(getSession());
+
+ assertThat(finder.findByVersion(currentProjectSnapshot, "1.1").getProjectSnapshotId(), is(1009));
+ }
+
+ @Test
+ public void shouldNotFindVersion() {
+ setupData("shared");
+
+ Snapshot currentProjectSnapshot = getSession().getSingleResult(Snapshot.class, "id", 1010);
+ PastSnapshotFinderByVersion finder = new PastSnapshotFinderByVersion(getSession());
+
+ assertThat(finder.findByVersion(currentProjectSnapshot, "1.0"), nullValue());
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderTest.java b/sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderTest.java
new file mode 100644
index 00000000000..eb59d8d4139
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotFinderTest.java
@@ -0,0 +1,183 @@
+/*
+ * 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.batch.components;
+
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.database.model.Snapshot;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import static junit.framework.Assert.assertNull;
+import static org.hamcrest.Matchers.nullValue;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class PastSnapshotFinderTest {
+
+ private PastSnapshotFinderByDays finderByDays;
+ private PastSnapshotFinderByDate finderByDate;
+ private PastSnapshotFinderByVersion finderByVersion;
+ private PastSnapshotFinderByPreviousAnalysis finderByPreviousAnalysis;
+ private PastSnapshotFinder finder;
+
+ @Before
+ public void initFinders() {
+ finderByDays = mock(PastSnapshotFinderByDays.class);
+ finderByDate = mock(PastSnapshotFinderByDate.class);
+ finderByVersion = mock(PastSnapshotFinderByVersion.class);
+ finderByPreviousAnalysis = mock(PastSnapshotFinderByPreviousAnalysis.class);
+ finder = new PastSnapshotFinder(finderByDays, finderByVersion, finderByDate, finderByPreviousAnalysis);
+ }
+
+ @Test
+ public void shouldFindByNumberOfDays() {
+ when(finderByDays.findFromDays(null, 30)).thenReturn(new PastSnapshot("days", null).setModeParameter("30"));
+
+ PastSnapshot variationSnapshot = finder.find(null, 1, "30");
+
+ verify(finderByDays).findFromDays(null, 30);
+ assertNotNull(variationSnapshot);
+ assertThat(variationSnapshot.getIndex(), is(1));
+ assertThat(variationSnapshot.getMode(), is("days"));
+ assertThat(variationSnapshot.getModeParameter(), is("30"));
+ }
+
+ @Test
+ public void shouldNotFindByNumberOfDays() {
+ PastSnapshot variationSnapshot = finder.find(null, 1, "30");
+
+ verify(finderByDays).findFromDays(null, 30);
+ assertNull(variationSnapshot);
+ }
+
+ @Test
+ public void shouldFindByDate() throws ParseException {
+ final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+ final Date date = format.parse("2010-05-18");
+ when(finderByDate.findByDate(null, date)).thenReturn(new PastSnapshot("date", new Snapshot()));
+
+ PastSnapshot variationSnapshot = finder.find(null, 2, "2010-05-18");
+
+ verify(finderByDate).findByDate((Snapshot) anyObject(), argThat(new BaseMatcher<Date>() {
+ public boolean matches(Object o) {
+ return o.equals(date);
+ }
+
+ public void describeTo(Description description) {
+
+ }
+ }));
+ assertThat(variationSnapshot.getIndex(), is(2));
+ assertThat(variationSnapshot.getMode(), is("date"));
+ assertThat(variationSnapshot.getProjectSnapshot(), not(nullValue()));
+ }
+
+ @Test
+ public void shouldNotFindByDate() throws ParseException {
+ when(finderByDate.findByDate((Snapshot) anyObject(), (Date) anyObject())).thenReturn(null);
+
+ PastSnapshot variationSnapshot = finder.find(null, 2, "2010-05-18");
+
+ verify(finderByDate).findByDate((Snapshot) anyObject(), (Date) anyObject());
+ assertNull(variationSnapshot);
+ }
+
+ @Test
+ public void shouldFindByPreviousAnalysis() throws ParseException {
+ final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+ final Date date = format.parse("2010-05-18");
+ Snapshot snapshot = new Snapshot();
+ snapshot.setCreatedAt(date);
+ when(finderByPreviousAnalysis.findByPreviousAnalysis(null)).thenReturn(new PastSnapshot(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS, snapshot));
+
+ PastSnapshot variationSnapshot = finder.find(null, 2, CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS);
+
+ verify(finderByPreviousAnalysis).findByPreviousAnalysis(null);
+ assertThat(variationSnapshot.getIndex(), is(2));
+ assertThat(variationSnapshot.getMode(), is(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS));
+ assertThat(variationSnapshot.getProjectSnapshot(), not(nullValue()));
+ }
+
+ @Test
+ public void shouldNotFindPreviousAnalysis() {
+ when(finderByPreviousAnalysis.findByPreviousAnalysis(null)).thenReturn(null);
+
+ PastSnapshot variationSnapshot = finder.find(null, 2, CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS);
+
+ verify(finderByPreviousAnalysis).findByPreviousAnalysis(null);
+
+ assertNull(variationSnapshot);
+ }
+
+ @Test
+ public void shouldFindByVersion() {
+ when(finderByVersion.findByVersion(null, "1.2")).thenReturn(new PastSnapshot("version", new Snapshot()));
+
+ PastSnapshot variationSnapshot = finder.find(null, 2, "1.2");
+
+ verify(finderByVersion).findByVersion(null, "1.2");
+ assertThat(variationSnapshot.getIndex(), is(2));
+ assertThat(variationSnapshot.getMode(), is("version"));
+ assertThat(variationSnapshot.getProjectSnapshot(), not(nullValue()));
+ }
+
+ @Test
+ public void shouldNotFindVersion() {
+ when(finderByVersion.findByVersion(null, "1.2")).thenReturn(null);
+
+ PastSnapshot variationSnapshot = finder.find(null, 2, "1.2");
+
+ verify(finderByVersion).findByVersion(null, "1.2");
+ assertNull(variationSnapshot);
+ }
+
+ @Test
+ public void shouldNotFailIfUnknownFormat() {
+ when(finderByPreviousAnalysis.findByPreviousAnalysis(null)).thenReturn(new PastSnapshot(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS, new Snapshot())); // should not be called
+ assertNull(finder.find(null, 2, "foooo"));
+ }
+
+ @Test
+ public void shouldGetPropertyValue() {
+ PropertiesConfiguration conf = new PropertiesConfiguration();
+ conf.setProperty("sonar.timemachine.period1", "5");
+
+ assertThat(PastSnapshotFinder.getPropertyValue(conf, 1), is("5"));
+ assertThat(PastSnapshotFinder.getPropertyValue(conf, 999), nullValue());
+ }
+
+ @Test
+ public void shouldGetDefaultPropertyValue() {
+ PropertiesConfiguration conf = new PropertiesConfiguration();
+ conf.setProperty("sonar.timemachine.period1", "5");
+
+ assertThat(PastSnapshotFinder.getPropertyValue(conf, 2), is(CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_2));
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/components/PastViolationsLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/components/PastViolationsLoaderTest.java
new file mode 100644
index 00000000000..bbc35b4b878
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/components/PastViolationsLoaderTest.java
@@ -0,0 +1,55 @@
+package org.sonar.batch.components;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.database.model.RuleFailureModel;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.resources.JavaFile;
+import org.sonar.api.resources.Resource;
+import org.sonar.batch.index.ResourcePersister;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+public class PastViolationsLoaderTest extends AbstractDbUnitTestCase {
+
+ private ResourcePersister resourcePersister;
+ private PastViolationsLoader loader;
+
+ @Before
+ public void setUp() {
+ setupData("shared");
+ resourcePersister = mock(ResourcePersister.class);
+ loader = new PastViolationsLoader(getSession(), resourcePersister);
+ }
+
+ @Test
+ public void shouldGetPastResourceViolations() {
+ Snapshot snapshot = getSession().getSingleResult(Snapshot.class, "id", 1000);
+ doReturn(snapshot).when(resourcePersister)
+ .getSnapshot(any(Resource.class));
+ doReturn(snapshot).when(resourcePersister)
+ .getLastSnapshot(any(Snapshot.class), anyBoolean());
+
+ List<RuleFailureModel> violations = loader.getPastViolations(new JavaFile("file"));
+
+ assertThat(violations.size(), is(2));
+ }
+
+ @Test
+ public void shouldReturnEmptyList() {
+ List<RuleFailureModel> violations = loader.getPastViolations(null);
+
+ assertThat(violations, notNullValue());
+ assertThat(violations.size(), is(0));
+ }
+
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/ViolationPersisterTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/ViolationPersisterTest.java
index f9bd575af86..b307e8dbe67 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/index/ViolationPersisterTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/index/ViolationPersisterTest.java
@@ -51,7 +51,6 @@ public class ViolationPersisterTest extends AbstractDbUnitTestCase {
Snapshot snapshot = getSession().getSingleResult(Snapshot.class, "id", 1000);
ResourcePersister resourcePersister = mock(ResourcePersister.class);
when(resourcePersister.saveResource((Project) anyObject(), eq(javaFile))).thenReturn(snapshot);
- when(resourcePersister.getLastSnapshot(snapshot, true)).thenReturn(snapshot);
when(resourcePersister.getSnapshot(javaFile)).thenReturn(snapshot);
violationPersister = new ViolationPersister(getSession(), resourcePersister, new DefaultRuleFinder(getSessionFactory()));
}
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/components/PastMeasuresLoaderTest/shared.xml b/sonar-batch/src/test/resources/org/sonar/batch/components/PastMeasuresLoaderTest/shared.xml
new file mode 100644
index 00000000000..bcde5f6100f
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/components/PastMeasuresLoaderTest/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" 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" 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 period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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 period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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 period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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]"
+ variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]" variation_value_4="[null]" variation_value_5="[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]"
+ variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]" variation_value_4="[null]" variation_value_5="[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]"
+ variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]" variation_value_4="[null]" variation_value_5="[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]"
+ variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]" variation_value_4="[null]" variation_value_5="[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]"
+ variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]" variation_value_4="[null]" variation_value_5="[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]"
+ variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]" variation_value_4="[null]" variation_value_5="[null]"/>
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/components/PastSnapshotFinderByDateTest/shared.xml b/sonar-batch/src/test/resources/org/sonar/batch/components/PastSnapshotFinderByDateTest/shared.xml
new file mode 100644
index 00000000000..87f673c46f4
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/components/PastSnapshotFinderByDateTest/shared.xml
@@ -0,0 +1,42 @@
+<dataset>
+
+ <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]"/>
+
+
+ <!-- 2008-11-01 -->
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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="1.1-SNAPSHOT" path=""
+ status="P" islast="false" depth="0" />
+
+
+ <!-- 2008-11-12 -->
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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-12 13:58:00.00" version="1.1-SNAPSHOT" path=""
+ status="P" islast="true" depth="0" />
+
+
+ <!-- 2008-11-22 -->
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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-22 13:58:00.00" version="1.1" path=""
+ status="P" islast="false" depth="0" />
+
+
+ <!-- 2008-11-25-->
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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-25 13:58:00.00" version="1.1" path=""
+ status="P" islast="false" depth="0" />
+
+ <!-- current analysis -->
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="1010"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ scope="PRJ" qualifier="TRK" created_at="2008-11-27 13:58:00.00" version="1.2-SNAPSHOT" path=""
+ status="U" islast="false" depth="0" />
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/components/PastSnapshotFinderByDaysTest/shared.xml b/sonar-batch/src/test/resources/org/sonar/batch/components/PastSnapshotFinderByDaysTest/shared.xml
new file mode 100644
index 00000000000..f9c84713489
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/components/PastSnapshotFinderByDaysTest/shared.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 : 2008-11-01-->
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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 period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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 period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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 unprocessed analysis - to ignore: 2008-11-12 -->
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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-12 13:58:00.00" version="[null]" path=""
+ status="U" islast="false" depth="0" />
+
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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-12 13:58:00.00" version="[null]" path="1003."
+ status="U" islast="false" depth="1" />
+
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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-12 13:58:00.00" version="[null]" path="1003.1004."
+ status="P" islast="false" depth="2" />
+
+
+ <!-- second analysis : 2008-11-13-->
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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-13 13:58:00.00" version="[null]" path=""
+ status="P" islast="true" depth="0" />
+
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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-13 13:58:00.00" version="[null]" path="1006."
+ status="P" islast="true" depth="1" />
+
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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-13 13:58:00.00" version="[null]" path="1006.1007."
+ status="P" islast="true" depth="2" />
+
+
+ <!-- current analysis : 2008-11-16 -->
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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-16 13:58:00.00" version="[null]" path=""
+ status="U" islast="false" depth="0" />
+
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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-16 13:58:00.00" version="[null]" path="1009."
+ status="U" islast="false" depth="1" />
+
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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-16 13:58:00.00" version="[null]" path="1009.1010."
+ status="U" islast="false" depth="2" />
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/components/PastSnapshotFinderByPreviousAnalysisTest/shouldFindPreviousAnalysis.xml b/sonar-batch/src/test/resources/org/sonar/batch/components/PastSnapshotFinderByPreviousAnalysisTest/shouldFindPreviousAnalysis.xml
new file mode 100644
index 00000000000..6b30b66983a
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/components/PastSnapshotFinderByPreviousAnalysisTest/shouldFindPreviousAnalysis.xml
@@ -0,0 +1,26 @@
+<dataset>
+
+ <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]"/>
+
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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-22 13:58:00.00" version="1.1" path=""
+ status="P" islast="false" depth="0" />
+
+
+ <!-- last analysis -->
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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-25 13:58:00.00" version="1.1" path=""
+ status="P" islast="true" depth="0" />
+
+ <!-- current analysis -->
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="1010"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ scope="PRJ" qualifier="TRK" created_at="2008-11-27 13:58:00.00" version="1.2-SNAPSHOT" path=""
+ status="U" islast="false" depth="0" />
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/components/PastSnapshotFinderByPreviousAnalysisTest/shouldNotFindPreviousAnalysis.xml b/sonar-batch/src/test/resources/org/sonar/batch/components/PastSnapshotFinderByPreviousAnalysisTest/shouldNotFindPreviousAnalysis.xml
new file mode 100644
index 00000000000..2ac2dbbc8a1
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/components/PastSnapshotFinderByPreviousAnalysisTest/shouldNotFindPreviousAnalysis.xml
@@ -0,0 +1,21 @@
+<dataset>
+
+ <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]"/>
+
+
+ <!-- unprocessed analysis -->
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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-25 13:58:00.00" version="1.1" path=""
+ status="U" islast="false" depth="0" />
+
+ <!-- current analysis -->
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="1010"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ scope="PRJ" qualifier="TRK" created_at="2008-11-27 13:58:00.00" version="1.2-SNAPSHOT" path=""
+ status="U" islast="false" depth="0" />
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/components/PastSnapshotFinderByVersionTest/shared.xml b/sonar-batch/src/test/resources/org/sonar/batch/components/PastSnapshotFinderByVersionTest/shared.xml
new file mode 100644
index 00000000000..72207acb9cc
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/components/PastSnapshotFinderByVersionTest/shared.xml
@@ -0,0 +1,42 @@
+<dataset>
+
+ <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]"/>
+
+
+ <!-- version 1.1-SNAPSHOT -->
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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="1.1-SNAPSHOT" path=""
+ status="P" islast="false" depth="0" />
+
+
+ <!-- version 1.1-SNAPSHOT -->
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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="1.1-SNAPSHOT" path=""
+ status="P" islast="true" depth="0" />
+
+
+ <!-- unprocessed version 1.1 (to ignore) -->
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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="1.1" path=""
+ status="U" islast="false" depth="0" />
+
+
+ <!-- version 1.1 -->
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" 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="1.1" path=""
+ status="P" islast="false" depth="0" />
+
+ <!-- current analysis -->
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="1010"
+ project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ scope="PRJ" qualifier="TRK" created_at="2008-11-05 13:58:00.00" version="1.2-SNAPSHOT" path=""
+ status="U" islast="false" depth="0" />
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/components/PastViolationsLoaderTest/shared.xml b/sonar-batch/src/test/resources/org/sonar/batch/components/PastViolationsLoaderTest/shared.xml
new file mode 100644
index 00000000000..a7a222fe037
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/components/PastViolationsLoaderTest/shared.xml
@@ -0,0 +1,24 @@
+<dataset>
+
+ <rules_categories id="1" name="Efficiency" description="[null]"/>
+ <rules_categories id="6" name="Usability" description="[null]"/>
+
+ <rules id="30" name="Check Header" 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" 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]"/>
+
+ <projects id="200" scope="FIL" qualifier="CLA" kee="project:org.foo.Bar" root_id="[null]"
+ name="Bar" long_name="org.foo.Bar" description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]" profile_id="[null]"/>
+
+ <snapshots period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="1000" project_id="200" parent_snapshot_id="[null]" root_project_id="100" root_snapshot_id="[null]"
+ scope="FIL" qualifier="CLA" created_at="2008-11-01 13:58:00.00" version="[null]" path=""
+ status="U" islast="false" depth="3" />
+
+ <RULE_FAILURES ID="1" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="old message" LINE="10" COST="[null]" created_at="2008-11-01 13:58:00.00" />
+ <RULE_FAILURES ID="2" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="old message" LINE="10" COST="[null]" created_at="2008-11-01 13:58:00.00" />
+</dataset>