]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-2202 Calculate variations on quality model measures
authorsimonbrandhof <simon.brandhof@gmail.com>
Wed, 27 Apr 2011 23:22:08 +0000 (01:22 +0200)
committersimonbrandhof <simon.brandhof@gmail.com>
Wed, 27 Apr 2011 23:22:08 +0000 (01:22 +0200)
plugins/sonar-core-plugin/derby.log [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/VariationDecorator.java
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/VariationDecoratorTest.java
sonar-batch/src/main/java/org/sonar/batch/components/PastMeasuresLoader.java
sonar-batch/src/test/java/org/sonar/batch/components/PastMeasuresLoaderTest.java

diff --git a/plugins/sonar-core-plugin/derby.log b/plugins/sonar-core-plugin/derby.log
new file mode 100644 (file)
index 0000000..bd143da
--- /dev/null
@@ -0,0 +1,91 @@
+----------------------------------------------------------------
+Thu Apr 28 00:17:56 CEST 2011:
+Booting Derby version The Apache Software Foundation - Apache Derby - 10.7.1.1 - (1040133): instance a816c00e-012f-990a-3e0e-000004e64398 
+on database directory memory:/Users/sbrandhof/projects/github/sonar/plugins/sonar-core-plugin/sonar  with class loader sun.misc.Launcher$AppClassLoader@1ef6a746 
+Loaded from file:/Users/sbrandhof/.m2/repository/org/apache/derby/derby/10.7.1.1/derby-10.7.1.1.jar
+java.vendor=Apple Inc.
+java.runtime.version=1.6.0_24-b07-334-10M3326
+Database Class Loader started - derby.database.classpath=''
+----------------------------------------------------------------
+Thu Apr 28 00:18:01 CEST 2011:
+Shutting down instance a816c00e-012f-990a-3e0e-000004e64398 on database directory memory:/Users/sbrandhof/projects/github/sonar/plugins/sonar-core-plugin/sonar with class loader sun.misc.Launcher$AppClassLoader@1ef6a746 
+Thu Apr 28 00:18:01 CEST 2011 Thread[main,5,main] Cleanup action starting
+java.sql.SQLException: Database 'memory:sonar' not found.
+       at org.apache.derby.impl.jdbc.SQLExceptionFactory40.getSQLException(Unknown Source)
+       at org.apache.derby.impl.jdbc.Util.newEmbedSQLException(Unknown Source)
+       at org.apache.derby.impl.jdbc.Util.newEmbedSQLException(Unknown Source)
+       at org.apache.derby.impl.jdbc.Util.generateCsSQLException(Unknown Source)
+       at org.apache.derby.impl.jdbc.EmbedConnection.newSQLException(Unknown Source)
+       at org.apache.derby.impl.jdbc.EmbedConnection.handleDBNotFound(Unknown Source)
+       at org.apache.derby.impl.jdbc.EmbedConnection.<init>(Unknown Source)
+       at org.apache.derby.impl.jdbc.EmbedConnection30.<init>(Unknown Source)
+       at org.apache.derby.impl.jdbc.EmbedConnection40.<init>(Unknown Source)
+       at org.apache.derby.jdbc.Driver40.getNewEmbedConnection(Unknown Source)
+       at org.apache.derby.jdbc.InternalDriver.connect(Unknown Source)
+       at org.apache.derby.jdbc.EmbeddedDriver.connect(Unknown Source)
+       at java.sql.DriverManager.getConnection(DriverManager.java:582)
+       at java.sql.DriverManager.getConnection(DriverManager.java:207)
+       at org.sonar.test.persistence.DatabaseTestCase.stopDatabase(DatabaseTestCase.java:89)
+       at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+       at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
+       at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
+       at java.lang.reflect.Method.invoke(Method.java:597)
+       at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
+       at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
+       at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
+       at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:37)
+       at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
+       at org.apache.maven.surefire.junit4.JUnit4TestSet.execute(JUnit4TestSet.java:59)
+       at org.apache.maven.surefire.suite.AbstractDirectoryTestSuite.executeTestSet(AbstractDirectoryTestSuite.java:120)
+       at org.apache.maven.surefire.suite.AbstractDirectoryTestSuite.execute(AbstractDirectoryTestSuite.java:145)
+       at org.apache.maven.surefire.Surefire.run(Surefire.java:104)
+       at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+       at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
+       at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
+       at java.lang.reflect.Method.invoke(Method.java:597)
+       at org.apache.maven.surefire.booter.SurefireBooter.runSuitesInProcess(SurefireBooter.java:290)
+       at org.apache.maven.surefire.booter.SurefireBooter.main(SurefireBooter.java:1017)
+Caused by: java.sql.SQLException: Database 'memory:sonar' not found.
+       at org.apache.derby.impl.jdbc.SQLExceptionFactory.getSQLException(Unknown Source)
+       at org.apache.derby.impl.jdbc.SQLExceptionFactory40.wrapArgsForTransportAcrossDRDA(Unknown Source)
+       ... 34 more
+============= begin nested exception, level (1) ===========
+java.sql.SQLException: Database 'memory:sonar' not found.
+       at org.apache.derby.impl.jdbc.SQLExceptionFactory.getSQLException(Unknown Source)
+       at org.apache.derby.impl.jdbc.SQLExceptionFactory40.wrapArgsForTransportAcrossDRDA(Unknown Source)
+       at org.apache.derby.impl.jdbc.SQLExceptionFactory40.getSQLException(Unknown Source)
+       at org.apache.derby.impl.jdbc.Util.newEmbedSQLException(Unknown Source)
+       at org.apache.derby.impl.jdbc.Util.newEmbedSQLException(Unknown Source)
+       at org.apache.derby.impl.jdbc.Util.generateCsSQLException(Unknown Source)
+       at org.apache.derby.impl.jdbc.EmbedConnection.newSQLException(Unknown Source)
+       at org.apache.derby.impl.jdbc.EmbedConnection.handleDBNotFound(Unknown Source)
+       at org.apache.derby.impl.jdbc.EmbedConnection.<init>(Unknown Source)
+       at org.apache.derby.impl.jdbc.EmbedConnection30.<init>(Unknown Source)
+       at org.apache.derby.impl.jdbc.EmbedConnection40.<init>(Unknown Source)
+       at org.apache.derby.jdbc.Driver40.getNewEmbedConnection(Unknown Source)
+       at org.apache.derby.jdbc.InternalDriver.connect(Unknown Source)
+       at org.apache.derby.jdbc.EmbeddedDriver.connect(Unknown Source)
+       at java.sql.DriverManager.getConnection(DriverManager.java:582)
+       at java.sql.DriverManager.getConnection(DriverManager.java:207)
+       at org.sonar.test.persistence.DatabaseTestCase.stopDatabase(DatabaseTestCase.java:89)
+       at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+       at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
+       at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
+       at java.lang.reflect.Method.invoke(Method.java:597)
+       at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
+       at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
+       at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
+       at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:37)
+       at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
+       at org.apache.maven.surefire.junit4.JUnit4TestSet.execute(JUnit4TestSet.java:59)
+       at org.apache.maven.surefire.suite.AbstractDirectoryTestSuite.executeTestSet(AbstractDirectoryTestSuite.java:120)
+       at org.apache.maven.surefire.suite.AbstractDirectoryTestSuite.execute(AbstractDirectoryTestSuite.java:145)
+       at org.apache.maven.surefire.Surefire.run(Surefire.java:104)
+       at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+       at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
+       at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
+       at java.lang.reflect.Method.invoke(Method.java:597)
+       at org.apache.maven.surefire.booter.SurefireBooter.runSuitesInProcess(SurefireBooter.java:290)
+       at org.apache.maven.surefire.booter.SurefireBooter.main(SurefireBooter.java:1017)
+============= end nested exception, level (1) ===========
+Cleanup action completed
index ac55d86ebf523e43eb1fcb4e5b02c4b6bc098d45..920436892a9a5c8cf108ab5a95633ef9945fc842 100644 (file)
  */
 package org.sonar.plugins.core.timemachine;
 
+import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.batch.*;
-import org.sonar.api.database.model.MeasureModel;
 import org.sonar.api.measures.*;
-import org.sonar.api.qualitymodel.Characteristic;
 import org.sonar.api.resources.Project;
 import org.sonar.api.resources.Resource;
 import org.sonar.api.resources.Scopes;
-import org.sonar.api.rules.RulePriority;
 import org.sonar.batch.components.PastMeasuresLoader;
 import org.sonar.batch.components.PastSnapshot;
 import org.sonar.batch.components.TimeMachineConfiguration;
@@ -41,15 +39,14 @@ import java.util.Map;
 public class VariationDecorator implements Decorator {
 
   private List<PastSnapshot> projectPastSnapshots;
-  private PastMeasuresLoader pastMeasuresLoader;
   private MetricFinder metricFinder;
+  private PastMeasuresLoader pastMeasuresLoader;
+
 
   public VariationDecorator(PastMeasuresLoader pastMeasuresLoader, MetricFinder metricFinder, TimeMachineConfiguration configuration) {
     this(pastMeasuresLoader, metricFinder, configuration.getProjectPastSnapshots());
-
   }
-
-  VariationDecorator(PastMeasuresLoader pastMeasuresLoader, MetricFinder metricFinder, List<PastSnapshot> projectPastSnapshots) {
+  public VariationDecorator(PastMeasuresLoader pastMeasuresLoader, MetricFinder metricFinder, List<PastSnapshot> projectPastSnapshots) {
     this.pastMeasuresLoader = pastMeasuresLoader;
     this.projectPastSnapshots = projectPastSnapshots;
     this.metricFinder = metricFinder;
@@ -78,30 +75,32 @@ public class VariationDecorator implements Decorator {
   }
 
   private void calculateVariation(Resource resource, DecoratorContext context, PastSnapshot pastSnapshot) {
-    List<MeasureModel> pastMeasures = pastMeasuresLoader.getPastMeasures(resource, pastSnapshot);
+    List<Object[]> pastMeasures = pastMeasuresLoader.getPastMeasures(resource, pastSnapshot);
     compareWithPastMeasures(context, pastSnapshot.getIndex(), pastMeasures);
   }
 
-  void compareWithPastMeasures(DecoratorContext context, int index, List<MeasureModel> pastMeasures) {
-    Map<MeasureKey, MeasureModel> pastMeasuresByKey = Maps.newHashMap();
-    for (MeasureModel pastMeasure : pastMeasures) {
+  void compareWithPastMeasures(DecoratorContext context, int index, List<Object[]> pastMeasures) {
+    Map<MeasureKey, Object[]> pastMeasuresByKey = Maps.newHashMap();
+    for (Object[] pastMeasure : pastMeasures) {
       pastMeasuresByKey.put(new MeasureKey(pastMeasure), pastMeasure);
     }
 
     // for each measure, search equivalent past measure
-    for (Measure measure : context.getMeasures(MeasuresFilters.all())) {
+    for (Measure measure : context.getMeasures(new CustomMeasureFilter())) {
       // compare with past measure
-      Integer metricId = (measure.getMetric().getId()!=null ? measure.getMetric().getId() : metricFinder.findByKey(measure.getMetric().getKey()).getId());
-      MeasureModel pastMeasure = pastMeasuresByKey.get(new MeasureKey(measure, metricId));
+      Integer metricId = (measure.getMetric().getId() != null ? measure.getMetric().getId() : metricFinder.findByKey(measure.getMetric().getKey()).getId());
+      Integer characteristicId = (measure.getCharacteristic() != null ? measure.getCharacteristic().getId() : null);
+
+      Object[] pastMeasure = pastMeasuresByKey.get(new MeasureKey(metricId, characteristicId));
       if (updateVariation(measure, pastMeasure, index)) {
         context.saveMeasure(measure);
       }
     }
   }
 
-  boolean updateVariation(Measure measure, MeasureModel pastMeasure, int index) {
-    if (pastMeasure != null && pastMeasure.getValue() != null && measure.getValue() != null) {
-      double variation = (measure.getValue().doubleValue() - pastMeasure.getValue().doubleValue());
+  boolean updateVariation(Measure measure, Object[] pastMeasure, int index) {
+    if (pastMeasure != null && PastMeasuresLoader.hasValue(pastMeasure) && measure.getValue() != null) {
+      double variation = (measure.getValue().doubleValue() - PastMeasuresLoader.getValue(pastMeasure));
       measure.setVariation(index, variation);
       return true;
     }
@@ -113,28 +112,30 @@ public class VariationDecorator implements Decorator {
     return getClass().getSimpleName();
   }
 
-  static class MeasureKey {
+  static class CustomMeasureFilter implements MeasuresFilter<Collection<Measure>> {
+    public Collection<Measure> filter(Collection<Measure> measures) {
+      List<Measure> result = Lists.newArrayList();
+      for (Measure measure : measures) {
+        if (!(measure instanceof RuleMeasure)) {
+          result.add(measure);
+        }
+      }
+      return result;
+    }
+  }
+
+  static final class MeasureKey {
     Integer metricId;
-    Integer ruleId;
-    RulePriority priority;
-    Characteristic characteristic;
-
-    MeasureKey(MeasureModel model) {
-      metricId = model.getMetricId();
-      ruleId = model.getRuleId();
-      priority = model.getRulePriority();
-      characteristic = model.getCharacteristic();
+    Integer characteristicId;
+
+    MeasureKey(Object[] pastFields) {
+      metricId = PastMeasuresLoader.getMetricId(pastFields);
+      characteristicId = PastMeasuresLoader.getCharacteristicId(pastFields);
     }
 
-    MeasureKey(Measure measure, Integer metricId) {
+    MeasureKey(Integer metricId, Integer characteristicId) {
       this.metricId = metricId;
-      this.characteristic = measure.getCharacteristic();
-      // TODO merge RuleMeasure into Measure
-      if (measure instanceof RuleMeasure) {
-        RuleMeasure rm = (RuleMeasure) measure;
-        this.ruleId = (rm.getRule() == null ? null : rm.getRule().getId());
-        this.priority = rm.getRulePriority();
-      }
+      this.characteristicId = characteristicId;
     }
 
     @Override
@@ -145,29 +146,20 @@ public class VariationDecorator implements Decorator {
       if (o == null || getClass() != o.getClass()) {
         return false;
       }
-
       MeasureKey that = (MeasureKey) o;
-      if (characteristic != null ? !characteristic.equals(that.characteristic) : that.characteristic != null) {
+      if (characteristicId != null ? !characteristicId.equals(that.characteristicId) : that.characteristicId != null) {
         return false;
       }
       if (!metricId.equals(that.metricId)) {
         return false;
       }
-      if (priority != that.priority) {
-        return false;
-      }
-      if (ruleId != null ? !ruleId.equals(that.ruleId) : that.ruleId != null) {
-        return false;
-      }
       return true;
     }
 
     @Override
     public int hashCode() {
       int result = metricId.hashCode();
-      result = 31 * result + (ruleId != null ? ruleId.hashCode() : 0);
-      result = 31 * result + (priority != null ? priority.hashCode() : 0);
-      result = 31 * result + (characteristic != null ? characteristic.hashCode() : 0);
+      result = 31 * result + (characteristicId != null ? characteristicId.hashCode() : 0);
       return result;
     }
   }
index 242f6f9439304aee1b2060037a917c6435977505..ad4fd12459efc956745e3dfab72edae0f9f92e92 100644 (file)
@@ -43,8 +43,11 @@ import static org.mockito.Mockito.*;
 
 public class VariationDecoratorTest extends AbstractDbUnitTestCase {
 
-  public static final Metric NCLOC = new Metric("ncloc").setId(12);
-  public static final Metric COVERAGE = new Metric("coverage").setId(16);
+  public static final int NCLOC_ID = 12;
+  public static final Metric NCLOC = new Metric("ncloc").setId(NCLOC_ID);
+
+  public static final int COVERAGE_ID = 16;
+  public static final Metric COVERAGE = new Metric("coverage").setId(COVERAGE_ID);
 
   @Test
   public void shouldNotCalculateVariationsOnFiles() {
@@ -67,12 +70,12 @@ public class VariationDecoratorTest extends AbstractDbUnitTestCase {
 
     // first past analysis
     when(pastMeasuresLoader.getPastMeasures(javaPackage, pastSnapshot1)).thenReturn(Arrays.asList(
-        newMeasureModel(NCLOC, 180.0),
-        newMeasureModel(COVERAGE, 75.0)));
+        new Object[]{NCLOC_ID, null, 180.0},
+        new Object[]{COVERAGE_ID, null, 75.0}));
 
     // second past analysis
-    when(pastMeasuresLoader.getPastMeasures(javaPackage, pastSnapshot3)).thenReturn(Arrays.asList(
-        newMeasureModel(NCLOC, 240.0)));
+    when(pastMeasuresLoader.getPastMeasures(javaPackage, pastSnapshot3)).thenReturn(Arrays.<Object[]>asList(
+        new Object[]{NCLOC_ID, null, 240.0}));
 
     // current analysis
     DecoratorContext context = mock(DecoratorContext.class);
@@ -80,7 +83,8 @@ public class VariationDecoratorTest extends AbstractDbUnitTestCase {
     Measure currentCoverage = newMeasure(COVERAGE, 80.0);
     when(context.getMeasures(Matchers.<MeasuresFilter>anyObject())).thenReturn(Arrays.asList(currentNcloc, currentCoverage));
 
-    VariationDecorator decorator = new VariationDecorator(pastMeasuresLoader, mock(MetricFinder.class), Arrays.asList(pastSnapshot1, pastSnapshot3));
+    VariationDecorator decorator = new VariationDecorator(pastMeasuresLoader, mock(MetricFinder.class),
+        Arrays.asList(pastSnapshot1, pastSnapshot3));
     decorator.decorate(javaPackage, context);
 
     // context updated for each variation : 2 times for ncloc and 1 time for coverage
index 19efcadd46db7a4e008f0702bf18520e72666ee6..95e5dff7beed06db708b85d855e2b4d7c8d76fd3 100644 (file)
@@ -23,7 +23,6 @@ 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;
@@ -57,24 +56,18 @@ public class PastMeasuresLoader implements BatchExtension {
     return metricByIds.values();
   }
 
-  public List<MeasureModel> getPastMeasures(Resource resource, PastSnapshot projectPastSnapshot) {
-    return getPastMeasures(resource, projectPastSnapshot.getProjectSnapshot());
-  }
-
-  public List<MeasureModel> getPastMeasures(Resource resource, Snapshot projectSnapshot) {
-    if (isPersisted(resource) && projectSnapshot!=null) {
-      return getPastMeasures(resource.getId(), projectSnapshot);
+  public List<Object[]> getPastMeasures(Resource resource, PastSnapshot projectPastSnapshot) {
+    if (isPersisted(resource) && projectPastSnapshot != null && projectPastSnapshot.getProjectSnapshot()!=null) {
+      return getPastMeasures(resource.getId(), projectPastSnapshot.getProjectSnapshot());
     }
     return Collections.emptyList();
   }
 
-  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)
+  public List<Object[]> getPastMeasures(int resourceId, Snapshot projectSnapshot) {
+    String sql = "select m.metric_id, m.characteristic_id, m.value from project_measures m, snapshots s" +
+        " where m.snapshot_id=s.id and m.metric_id in (:metricIds) and m.rule_id is null and m.rule_priority is null " +
+        " and (s.root_snapshot_id=:rootSnapshotId or s.id=:rootSnapshotId) and s.project_id=:resourceId and s.status=:status";
+    return session.createNativeQuery(sql)
         .setParameter("metricIds", metricByIds.keySet())
         .setParameter("rootSnapshotId", ObjectUtils.defaultIfNull(projectSnapshot.getRootId(), projectSnapshot.getId()))
         .setParameter("resourceId", resourceId)
@@ -83,6 +76,22 @@ public class PastMeasuresLoader implements BatchExtension {
   }
 
   private boolean isPersisted(Resource resource) {
-    return resource.getId()!=null;
+    return resource.getId() != null;
+  }
+
+  public static Integer getMetricId(Object[] row) {
+    return (Integer) row[0];
+  }
+
+  public static Integer getCharacteristicId(Object[] row) {
+    return (Integer) row[1];
+  }
+
+  public static boolean hasValue(Object[] row) {
+    return row[2] != null;
+  }
+
+  public static double getValue(Object[] row) {
+    return ((Number) row[2]).doubleValue();
   }
 }
index 2b2137eb28819e01ce888c9d4bc346a9ad72f0c9..74e19b28ddf293385d6aac0e04527970213fb9ac 100644 (file)
@@ -32,6 +32,7 @@ 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.hamcrest.Matchers.nullValue;
 import static org.junit.internal.matchers.IsCollectionContaining.hasItems;
 
 public class PastMeasuresLoaderTest extends AbstractDbUnitTestCase {
@@ -48,13 +49,18 @@ public class PastMeasuresLoaderTest extends AbstractDbUnitTestCase {
     Snapshot projectSnapshot = getSession().getSingleResult(Snapshot.class, "id", PROJECT_SNAPSHOT_ID);
 
     PastMeasuresLoader loader = new PastMeasuresLoader(getSession(), metrics);
-    List<MeasureModel> measures = loader.getPastMeasures(FILE_ID, projectSnapshot);
+    List<Object[]> 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)));
-    }
+    Object[] pastMeasure = measures.get(0);
+    assertThat(PastMeasuresLoader.getMetricId(pastMeasure), is(1));
+    assertThat(PastMeasuresLoader.getCharacteristicId(pastMeasure), nullValue());
+    assertThat(PastMeasuresLoader.getValue(pastMeasure), is(5.0));
+
+    pastMeasure = measures.get(1);
+    assertThat(PastMeasuresLoader.getMetricId(pastMeasure), is(2));
+    assertThat(PastMeasuresLoader.getCharacteristicId(pastMeasure), nullValue());
+    assertThat(PastMeasuresLoader.getValue(pastMeasure), is(60.0));
   }
 
   @Test
@@ -65,13 +71,18 @@ public class PastMeasuresLoaderTest extends AbstractDbUnitTestCase {
     Snapshot projectSnapshot = getSession().getSingleResult(Snapshot.class, "id", PROJECT_SNAPSHOT_ID);
 
     PastMeasuresLoader loader = new PastMeasuresLoader(getSession(), metrics);
-    List<MeasureModel> measures = loader.getPastMeasures(PROJECT_ID, projectSnapshot);
+    List<Object[]> 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)));
-    }
+    Object[] pastMeasure = measures.get(0);
+    assertThat(PastMeasuresLoader.getMetricId(pastMeasure), is(1));
+    assertThat(PastMeasuresLoader.getCharacteristicId(pastMeasure), nullValue());
+    assertThat(PastMeasuresLoader.getValue(pastMeasure), is(60.0));
+
+    pastMeasure = measures.get(1);
+    assertThat(PastMeasuresLoader.getMetricId(pastMeasure), is(2));
+    assertThat(PastMeasuresLoader.getCharacteristicId(pastMeasure), nullValue());
+    assertThat(PastMeasuresLoader.getValue(pastMeasure), is(80.0));
   }
 
   @Test