aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-batch
diff options
context:
space:
mode:
authorsimonbrandhof <simon.brandhof@gmail.com>2010-11-26 12:17:53 +0000
committersimonbrandhof <simon.brandhof@gmail.com>2010-11-26 12:17:53 +0000
commite4381e4c5a7b6c179c04a8741258049d292fcd1e (patch)
tree082c632855e0a30626261f1503fe6f083c35ef4d /sonar-batch
parent33fe1a9edbef6982cd539026aba6b5827edb03b5 (diff)
downloadsonarqube-e4381e4c5a7b6c179c04a8741258049d292fcd1e.tar.gz
sonarqube-e4381e4c5a7b6c179c04a8741258049d292fcd1e.zip
SONAR-249 fix update of measures when using the delayed mode
Diffstat (limited to 'sonar-batch')
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java22
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/index/DefaultResourcePersister.java35
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java45
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/index/ViolationPersister.java16
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/index/DefaultIndexTest.java20
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/index/DefaultResourcePersisterTest.java12
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/index/MeasurePersisterTest.java18
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/index/ViolationPersisterTest.java69
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shared.xml22
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shouldSaveViolations-result.xml24
10 files changed, 213 insertions, 70 deletions
diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java
index d8c411c1285..0418c0b65e3 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java
@@ -22,10 +22,12 @@ package org.sonar.batch.index;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
+import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.Event;
import org.sonar.api.batch.SonarIndex;
+import org.sonar.api.database.model.ResourceModel;
import org.sonar.api.design.Dependency;
import org.sonar.api.measures.*;
import org.sonar.api.profiles.RulesProfile;
@@ -103,10 +105,6 @@ public final class DefaultIndex extends SonarIndex {
this.profile = profile;
}
- public PersistenceManager getPersistenceManager() {
- return persistence;
- }
-
/**
* Keep only project stuff
*/
@@ -136,8 +134,6 @@ public final class DefaultIndex extends SonarIndex {
/**
* Does nothing if the resource is already registered.
- *
- * @param resource
*/
public Resource addResource(Resource resource) {
getOrAddBucket(resource);
@@ -162,6 +158,7 @@ public final class DefaultIndex extends SonarIndex {
LOG.warn("The following resource has not been registered before saving data: " + resource);
}
+ resource.setEffectiveKey(calculateResourceEffectiveKey(currentProject, resource));
bucket = new Bucket(resource);
Bucket parentBucket = null;
Resource parent = resource.getParent();
@@ -180,6 +177,19 @@ public final class DefaultIndex extends SonarIndex {
return bucket;
}
+ static String calculateResourceEffectiveKey(Project project, Resource resource) {
+ String effectiveKey = resource.getKey();
+ if (!StringUtils.equals(Resource.SCOPE_SET, resource.getScope())) {
+ // not a project nor a library
+ effectiveKey = new StringBuilder(ResourceModel.KEY_SIZE)
+ .append(project.getKey())
+ .append(':')
+ .append(resource.getKey())
+ .toString();
+ }
+ return effectiveKey;
+ }
+
private boolean checkExclusion(Resource resource, Bucket parent) {
boolean excluded = (parent != null && parent.isExcluded()) || (resourceFilters != null && resourceFilters.isExcluded(resource));
resource.setExcluded(excluded);
diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultResourcePersister.java b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultResourcePersister.java
index 82a3dc0ffe4..e4215524033 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultResourcePersister.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultResourcePersister.java
@@ -70,7 +70,10 @@ public final class DefaultResourcePersister implements ResourcePersister {
}
private Snapshot doSaveProject(Project project) {
- ResourceModel model = findOrCreateModel(project, project.getKey());
+ // temporary hack
+ project.setEffectiveKey(project.getKey());
+
+ ResourceModel model = findOrCreateModel(project);
model.setLanguageKey(project.getLanguageKey());// ugly, only for projects
Snapshot parentSnapshot = null;
@@ -111,7 +114,7 @@ public final class DefaultResourcePersister implements ResourcePersister {
}
private Snapshot doSaveLibrary(Project project, Library library) {
- ResourceModel model = findOrCreateModel(library, library.getKey());
+ ResourceModel model = findOrCreateModel(library);
model = session.save(model);
library.setId(model.getId()); // TODO to be removed
library.setEffectiveKey(library.getKey());
@@ -150,13 +153,11 @@ public final class DefaultResourcePersister implements ResourcePersister {
* Everything except project and library
*/
private Snapshot doSaveResource(Project project, Resource resource) {
- String databaseKey = getDatabaseKey(project, resource);
- ResourceModel model = findOrCreateModel(resource, databaseKey);
+ ResourceModel model = findOrCreateModel(resource);
Snapshot projectSnapshot = snapshotsByResource.get(project);
model.setRootId(projectSnapshot.getResourceId());
model = session.save(model);
resource.setId(model.getId()); // TODO to be removed
- resource.setEffectiveKey(databaseKey);
Snapshot parentSnapshot = (Snapshot)ObjectUtils.defaultIfNull(getSnapshot(resource.getParent()), projectSnapshot);
Snapshot snapshot = new Snapshot(model, parentSnapshot);
@@ -177,12 +178,12 @@ public final class DefaultResourcePersister implements ResourcePersister {
}
- private ResourceModel findOrCreateModel(Resource resource, String databaseKey) {
+ private ResourceModel findOrCreateModel(Resource resource) {
ResourceModel model;
try {
- model = session.getSingleResult(ResourceModel.class, "key", databaseKey);
+ model = session.getSingleResult(ResourceModel.class, "key", resource.getEffectiveKey());
if (model == null) {
- model = createModel(resource, databaseKey);
+ model = createModel(resource);
} else {
mergeModel(model, resource);
@@ -190,15 +191,15 @@ public final class DefaultResourcePersister implements ResourcePersister {
return model;
} catch (NonUniqueResultException e) {
- throw new SonarException("The resource '" + databaseKey + "' is duplicated in database.");
+ throw new SonarException("The resource '" + resource.getEffectiveKey() + "' is duplicated in database.");
}
}
- static ResourceModel createModel(Resource resource, String databaseKey) {
+ static ResourceModel createModel(Resource resource) {
ResourceModel model = new ResourceModel();
model.setEnabled(Boolean.TRUE);
model.setDescription(resource.getDescription());
- model.setKey(databaseKey);
+ model.setKey(resource.getEffectiveKey());
if (resource.getLanguage() != null) {
model.setLanguageKey(resource.getLanguage().getKey());
}
@@ -232,16 +233,4 @@ public final class DefaultResourcePersister implements ResourcePersister {
model.setLanguageKey(resource.getLanguage().getKey());
}
}
-
- static String getDatabaseKey(Project project, Resource resource) {
- if (StringUtils.equals(Resource.SCOPE_SET, resource.getScope())) {
- // projects + libraries
- return resource.getKey();
- }
- return new StringBuilder(ResourceModel.KEY_SIZE)
- .append(project.getKey())
- .append(':')
- .append(resource.getKey())
- .toString();
- }
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java b/sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java
index bd50db6f8b3..75eb3504d1f 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java
@@ -19,7 +19,8 @@
*/
package org.sonar.batch.index;
-import com.google.common.collect.Sets;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.SetMultimap;
import org.apache.commons.lang.math.NumberUtils;
import org.slf4j.LoggerFactory;
import org.sonar.api.database.DatabaseSession;
@@ -32,13 +33,12 @@ import org.sonar.api.resources.Project;
import org.sonar.api.resources.Resource;
import org.sonar.api.resources.ResourceUtils;
-import java.util.Iterator;
-import java.util.Set;
+import java.util.Map;
public final class MeasurePersister {
private boolean delayedMode = false;
- private Set<MeasureModel> unsavedMeasures = Sets.newLinkedHashSet();
+ private SetMultimap<Integer, Measure> unsavedMeasuresBySnapshotId = HashMultimap.create();
private DatabaseSession session;
private ResourcePersister resourcePersister;
@@ -59,24 +59,20 @@ public final class MeasurePersister {
Snapshot snapshot = resourcePersister.saveResource(project, resource);
if (snapshot != null) {
if (delayedMode && measure.getPersistenceMode().useMemory()) {
- MeasureModel model = createModel(measure);
- model.setSnapshotId(snapshot.getId());
- unsavedMeasures.add(model);
+ unsavedMeasuresBySnapshotId.put(snapshot.getId(), measure);
- } else if (shouldPersistMeasure(resource, measure)) {
- if (measure.getId() != null) {
- // update
- MeasureModel model = session.reattach(MeasureModel.class, measure.getId());
- model = mergeModel(measure, model);
- model.save(session);
+ } else if (measure.getId() != null) {
+ // update
+ MeasureModel model = session.reattach(MeasureModel.class, measure.getId());
+ model = mergeModel(measure, model);
+ model.save(session);
- } else {
- // insert
- MeasureModel model = createModel(measure);
- model.setSnapshotId(snapshot.getId());
- model.save(session);
- measure.setId(model.getId()); // could be removed
- }
+ } else if (shouldPersistMeasure(resource, measure)) {
+ // insert
+ MeasureModel model = createModel(measure);
+ model.setSnapshotId(snapshot.getId());
+ model.save(session);
+ measure.setId(model.getId()); // could be removed
}
}
}
@@ -92,13 +88,14 @@ public final class MeasurePersister {
}
public void dump() {
- LoggerFactory.getLogger(getClass()).debug("{} measures to dump", unsavedMeasures.size());
- for (Iterator<MeasureModel> it = unsavedMeasures.iterator(); it.hasNext();) {
- MeasureModel model = it.next();
+ LoggerFactory.getLogger(getClass()).debug("{} measures to dump", unsavedMeasuresBySnapshotId.size());
+ for (Map.Entry<Integer, Measure> entry : unsavedMeasuresBySnapshotId.entries()) {
+ MeasureModel model = createModel(entry.getValue());
+ model.setSnapshotId(entry.getKey());
model.save(session);
- it.remove();
}
session.commit();
+ unsavedMeasuresBySnapshotId.clear();
}
MeasureModel createModel(Measure measure) {
diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/ViolationPersister.java b/sonar-batch/src/main/java/org/sonar/batch/index/ViolationPersister.java
index 8d893ac2ef8..caf426f0cfd 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/index/ViolationPersister.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/index/ViolationPersister.java
@@ -62,18 +62,16 @@ public final class ViolationPersister {
private Integer getRuleId(Rule rule) {
Integer ruleId = ruleIds.get(rule);
if (ruleId == null) {
- Rule persistedRule = session.getSingleResult(Rule.class, "pluginName", rule.getRepositoryKey(), "key", rule.getKey(), "enabled", true);
- if (persistedRule == null) {
- throw new SonarException("Rule not found: " + rule);
+ ruleId = rule.getId();
+ if (ruleId == null) {
+ Rule persistedRule = session.getSingleResult(Rule.class, "pluginName", rule.getRepositoryKey(), "key", rule.getKey(), "enabled", true);
+ if (persistedRule == null) {
+ throw new SonarException("Rule not found: " + rule);
+ }
+ ruleId = persistedRule.getId();
}
- ruleId = persistedRule.getId();
ruleIds.put(rule, ruleId);
-
}
return ruleId;
}
-
- public void clear() {
- ruleIds.clear();
- }
}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/DefaultIndexTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/DefaultIndexTest.java
index 428a92f37d3..67a0e4e36d6 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/index/DefaultIndexTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/index/DefaultIndexTest.java
@@ -19,9 +19,25 @@
*/
package org.sonar.batch.index;
-import org.junit.Ignore;
+import org.junit.Test;
+import org.sonar.api.resources.JavaPackage;
+import org.sonar.api.resources.Library;
+import org.sonar.api.resources.Project;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
-@Ignore("to do")
public class DefaultIndexTest {
+ @Test
+ public void shouldCalculateResourceEffectiveKey() {
+ Project project = new Project("my_project");
+ assertThat(DefaultIndex.calculateResourceEffectiveKey(project, project), is("my_project"));
+
+ JavaPackage javaPackage = new JavaPackage("org.foo");
+ assertThat(DefaultIndex.calculateResourceEffectiveKey(project, javaPackage), is("my_project:org.foo"));
+
+ Library library = new Library("junit:junit", "4.7");
+ assertThat(DefaultIndex.calculateResourceEffectiveKey(project, library), is("junit:junit"));
+ }
}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/DefaultResourcePersisterTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/DefaultResourcePersisterTest.java
index 8134742fc15..bfd8a913dc6 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/index/DefaultResourcePersisterTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/index/DefaultResourcePersisterTest.java
@@ -90,7 +90,7 @@ public class DefaultResourcePersisterTest extends AbstractDbUnitTestCase {
ResourcePersister persister = new DefaultResourcePersister(getSession());
persister.saveProject(singleProject);
- persister.saveResource(singleProject, new JavaPackage("org.foo"));
+ persister.saveResource(singleProject, new JavaPackage("org.foo").setEffectiveKey("foo:org.foo"));
// check that the directory is attached to the project
checkTables("shouldSaveNewDirectory", "projects", "snapshots");
@@ -102,9 +102,9 @@ public class DefaultResourcePersisterTest extends AbstractDbUnitTestCase {
ResourcePersister persister = new DefaultResourcePersister(getSession());
persister.saveProject(singleProject);
- persister.saveResource(singleProject, new Library("junit:junit", "4.8.2"));
- persister.saveResource(singleProject, new Library("junit:junit", "4.8.2"));// do nothing, already saved
- persister.saveResource(singleProject, new Library("junit:junit", "3.2"));
+ persister.saveResource(singleProject, new Library("junit:junit", "4.8.2").setEffectiveKey("junit:junit"));
+ persister.saveResource(singleProject, new Library("junit:junit", "4.8.2").setEffectiveKey("junit:junit"));// do nothing, already saved
+ persister.saveResource(singleProject, new Library("junit:junit", "3.2").setEffectiveKey("junit:junit"));
checkTables("shouldSaveNewLibrary", "projects", "snapshots");
}
@@ -116,8 +116,8 @@ public class DefaultResourcePersisterTest extends AbstractDbUnitTestCase {
DefaultResourcePersister persister = new DefaultResourcePersister(getSession());
persister.saveProject(multiModuleProject);
persister.saveProject(moduleA);
- persister.saveResource(moduleA, new JavaPackage("org.foo"));
- persister.saveResource(moduleA, new JavaFile("org.foo.MyClass"));
+ persister.saveResource(moduleA, new JavaPackage("org.foo").setEffectiveKey("a:org.foo"));
+ persister.saveResource(moduleA, new JavaFile("org.foo.MyClass").setEffectiveKey("a:org.foo.MyClass"));
persister.clear();
assertThat(persister.getSnapshotsByResource().size(), is(2));
diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/MeasurePersisterTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/MeasurePersisterTest.java
index bf3b284a1b5..9d0e2b9e599 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/index/MeasurePersisterTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/index/MeasurePersisterTest.java
@@ -32,6 +32,8 @@ import org.sonar.api.resources.JavaPackage;
import org.sonar.api.resources.Project;
import org.sonar.jpa.test.AbstractDbUnitTestCase;
+import java.util.List;
+
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.anyObject;
@@ -81,6 +83,22 @@ public class MeasurePersisterTest extends AbstractDbUnitTestCase {
}
@Test
+ public void shouldAddDelayedMeasureSeveralTimes() {
+ measurePersister.setDelayedMode(true);
+ Measure measure = new Measure(ncloc).setValue(200.0);
+ measurePersister.saveMeasure(project, measure);
+
+ measure.setValue(300.0);
+ measurePersister.saveMeasure(project, measure);
+
+ measurePersister.dump();
+
+ List<MeasureModel> coverageMeasures = getSession().getResults(MeasureModel.class, "snapshotId", 3001, "metricId", 1);
+ assertThat(coverageMeasures.size(), is(1));
+ assertThat(coverageMeasures.get(0).getValue(), is(300.0));
+ }
+
+ @Test
@Ignore("to do")
public void shouldInsertDataMeasure() {
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
new file mode 100644
index 00000000000..307295c2e29
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/index/ViolationPersisterTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.index;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.resources.JavaFile;
+import org.sonar.api.resources.Project;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RulePriority;
+import org.sonar.api.rules.Violation;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ViolationPersisterTest extends AbstractDbUnitTestCase {
+
+ private ViolationPersister violationPersister;
+ private Rule rule1 = Rule.create("checkstyle", "com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck", "Check Header");
+ private Rule rule2 = Rule.create("checkstyle", "com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck", "Equals Avoid Null");
+ private JavaFile javaFile = new JavaFile("org.foo.Bar");
+
+ @Before
+ public void before() {
+ setupData("shared");
+ Snapshot snapshot = getSession().getSingleResult(Snapshot.class, "id", 1000);
+ ResourcePersister resourcePersister = mock(ResourcePersister.class);
+ when(resourcePersister.saveResource((Project) anyObject(), eq(javaFile))).thenReturn(snapshot);
+ violationPersister = new ViolationPersister(getSession(), resourcePersister);
+ }
+
+ @Test
+ public void shouldSaveViolations() {
+ Violation violation1a = Violation.create(rule1, javaFile)
+ .setPriority(RulePriority.CRITICAL).setLineId(20).setCost(55.6)
+ .setMessage("the message");
+ Violation violation1b = Violation.create(rule1, javaFile)
+ .setPriority(RulePriority.CRITICAL).setLineId(50).setCost(80.0);
+ Violation violation2 = Violation.create(rule2, javaFile)
+ .setPriority(RulePriority.MINOR);
+
+ violationPersister.saveViolation(new Project("project"), violation1a);
+ violationPersister.saveViolation(new Project("project"), violation1b);
+ violationPersister.saveViolation(new Project("project"), violation2);
+
+ checkTables("shouldSaveViolations", "rule_failures");
+ }
+}
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shared.xml b/sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shared.xml
new file mode 100644
index 00000000000..b9678a48fcc
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shared.xml
@@ -0,0 +1,22 @@
+<dataset>
+
+ <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]"/>
+
+ <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 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" />
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shouldSaveViolations-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shouldSaveViolations-result.xml
new file mode 100644
index 00000000000..1e78b98346e
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shouldSaveViolations-result.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" 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]"/>
+
+ <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 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="the message" LINE="20" COST="55.6"/>
+ <RULE_FAILURES ID="2" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="[null]" LINE="50" COST="80"/>
+ <RULE_FAILURES ID="3" SNAPSHOT_ID="1000" RULE_ID="31" FAILURE_LEVEL="1" MESSAGE="[null]" LINE="[null]" COST="[null]"/>
+</dataset> \ No newline at end of file