]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-249 fix update of measures when using the delayed mode
authorsimonbrandhof <simon.brandhof@gmail.com>
Fri, 26 Nov 2010 12:17:53 +0000 (12:17 +0000)
committersimonbrandhof <simon.brandhof@gmail.com>
Fri, 26 Nov 2010 12:17:53 +0000 (12:17 +0000)
sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java
sonar-batch/src/main/java/org/sonar/batch/index/DefaultResourcePersister.java
sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java
sonar-batch/src/main/java/org/sonar/batch/index/ViolationPersister.java
sonar-batch/src/test/java/org/sonar/batch/index/DefaultIndexTest.java
sonar-batch/src/test/java/org/sonar/batch/index/DefaultResourcePersisterTest.java
sonar-batch/src/test/java/org/sonar/batch/index/MeasurePersisterTest.java
sonar-batch/src/test/java/org/sonar/batch/index/ViolationPersisterTest.java [new file with mode: 0644]
sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shared.xml [new file with mode: 0644]
sonar-batch/src/test/resources/org/sonar/batch/index/ViolationPersisterTest/shouldSaveViolations-result.xml [new file with mode: 0644]

index d8c411c12850eddd794bc76de506aac2f1e01139..0418c0b65e3b6c9fc7a597153afebbe43c238d9e 100644 (file)
@@ -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);
index 82a3dc0ffe41982737811c3118012c8c9f93536b..e4215524033d8bd0d88b4ec055d254cfc5cf7755 100644 (file)
@@ -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();
-  }
 }
index bd50db6f8b333b34f79f4187df2cd184d5de1610..75eb3504d1f2bc5d6d7e843a42d4fb273083719e 100644 (file)
@@ -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) {
index 8d893ac2ef84dda8e7621fe90b49852f8a38456e..caf426f0cfd816e62b5986d54378c84a15675c77 100644 (file)
@@ -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();
-  }
 }
index 428a92f37d345dfb93c77fe4fbb29586251a1524..67a0e4e36d6525be0c048a8fb3c68973303c46a8 100644 (file)
  */
 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"));
+  }
 }
index 8134742fc15e15c8c50bb7f4867c66d8eb29a7a2..bfd8a913dc6385e6af4214560804f067a0d539ee 100644 (file)
@@ -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));
index bf3b284a1b585c85553398a8529f3bf6de6d08d5..9d0e2b9e5990019343a0fec42eedf8cf42569aa8 100644 (file)
@@ -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;
@@ -80,6 +82,22 @@ public class MeasurePersisterTest extends AbstractDbUnitTestCase {
     checkTables("shouldUpdateMeasure", "project_measures");
   }
 
+  @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 (file)
index 0000000..307295c
--- /dev/null
@@ -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 (file)
index 0000000..b9678a4
--- /dev/null
@@ -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 (file)
index 0000000..1e78b98
--- /dev/null
@@ -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