]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-2505 support measure variations
authorSimon Brandhof <simon.brandhof@gmail.com>
Tue, 21 Jun 2011 13:12:11 +0000 (15:12 +0200)
committerSimon Brandhof <simon.brandhof@gmail.com>
Tue, 21 Jun 2011 13:13:48 +0000 (15:13 +0200)
26 files changed:
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/NewCoverageFileAnalyzer.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/NewViolationsDecorator.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/TendencyDecorator.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/TimeMachineConfiguration.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/TimeMachineConfigurationPersister.java
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/NewViolationsDecoratorTest.java
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/TendencyDecoratorTest.java
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/TimeMachineConfigurationPersisterTest.java
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/TimeMachineConfigurationTest.java
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/VariationDecoratorTest.java
plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/TimeMachineConfigurationTest/shared.xml [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/bootstrap/ProjectModule.java
sonar-batch/src/main/java/org/sonar/batch/components/PastMeasuresLoader.java
sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshot.java
sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinder.java
sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinderByDate.java
sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinderByDays.java
sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinderByVersion.java
sonar-batch/src/main/java/org/sonar/batch/components/TimeMachineConfiguration.java [deleted file]
sonar-batch/src/test/java/org/sonar/batch/components/PastMeasuresLoaderTest.java
sonar-batch/src/test/java/org/sonar/batch/components/PastSnapshotTest.java
sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java
sonar-plugin-api/src/main/java/org/sonar/api/resources/Project.java
sonar-plugin-api/src/test/java/org/sonar/api/resources/ProjectTest.java

index c14be8d33108de2609eab1048f41c5fbdcde0428..f3b72b9e0c1925beaf5fbd99a05b65457025d270 100644 (file)
@@ -217,6 +217,7 @@ public class CorePlugin extends SonarPlugin {
     extensions.add(ReferenceAnalysis.class);
 
     // time machine
+    extensions.add(TimeMachineConfiguration.class);
     extensions.add(TendencyDecorator.class);
     extensions.add(VariationDecorator.class);
     extensions.add(ViolationTrackingDecorator.class);
index d44ffd056e1c9646a81da437aa54da48b72ed6f9..666d89082b576c9b521992e15a5a5cc98d1dbc17 100644 (file)
@@ -32,7 +32,6 @@ import org.sonar.api.resources.Resource;
 import org.sonar.api.resources.Scopes;
 import org.sonar.api.utils.KeyValueFormat;
 import org.sonar.batch.components.PastSnapshot;
-import org.sonar.batch.components.TimeMachineConfiguration;
 import org.sonar.core.NotDryRun;
 
 import java.util.Arrays;
index 86a150e28cdcba5020c5a22eee44125974917d2f..4e00eb4c99fb00fa219962f404d7bbf68d9b1257 100644 (file)
@@ -31,7 +31,6 @@ import org.sonar.api.rules.Rule;
 import org.sonar.api.rules.RulePriority;
 import org.sonar.api.rules.Violation;
 import org.sonar.batch.components.PastSnapshot;
-import org.sonar.batch.components.TimeMachineConfiguration;
 import org.sonar.core.NotDryRun;
 
 import java.util.*;
index ade65afb788b3be99ff2bc48f81a232dee38cd01..5a52622f9ec3e45a15cc2ad741566e249815d13f 100644 (file)
@@ -31,7 +31,6 @@ import org.sonar.api.measures.MetricFinder;
 import org.sonar.api.resources.Project;
 import org.sonar.api.resources.Resource;
 import org.sonar.api.resources.Scopes;
-import org.sonar.batch.components.TimeMachineConfiguration;
 import org.sonar.core.NotDryRun;
 
 import java.util.List;
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/TimeMachineConfiguration.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/TimeMachineConfiguration.java
new file mode 100644 (file)
index 0000000..0ef49a9
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.plugins.core.timemachine;
+
+import com.google.common.collect.Lists;
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.BatchExtension;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.utils.Logs;
+import org.sonar.batch.components.PastSnapshot;
+import org.sonar.batch.components.PastSnapshotFinder;
+
+import javax.persistence.Query;
+import java.util.Collections;
+import java.util.List;
+
+public class TimeMachineConfiguration implements BatchExtension {
+
+  private static final int NUMBER_OF_VARIATION_SNAPSHOTS = 5;
+
+  private Project project;
+  private final Configuration configuration;
+  private List<PastSnapshot> projectPastSnapshots;
+  private DatabaseSession session;
+
+  public TimeMachineConfiguration(DatabaseSession session, Project project, Configuration configuration, PastSnapshotFinder pastSnapshotFinder) {
+    this.session = session;
+    this.project = project;
+    this.configuration = configuration;
+    initPastSnapshots(pastSnapshotFinder);
+  }
+
+  private void initPastSnapshots(PastSnapshotFinder pastSnapshotFinder) {
+    Snapshot projectSnapshot = buildProjectSnapshot();
+
+    projectPastSnapshots = Lists.newLinkedList();
+    if (projectSnapshot != null) {
+      for (int index = 1; index <= NUMBER_OF_VARIATION_SNAPSHOTS; index++) {
+        PastSnapshot pastSnapshot = pastSnapshotFinder.find(projectSnapshot, configuration, index);
+        if (pastSnapshot != null) {
+          log(pastSnapshot);
+          projectPastSnapshots.add(pastSnapshot);
+        }
+      }
+    }
+  }
+
+  private Snapshot buildProjectSnapshot() {
+    Query query = session.createNativeQuery("select p.id from projects p where p.kee=:resourceKey and p.qualifier<>:lib and p.enabled=:enabled");
+    query.setParameter("resourceKey", project.getKey());
+    query.setParameter("lib", Qualifiers.LIBRARY);
+    query.setParameter("enabled", Boolean.TRUE);
+
+    Snapshot snapshot = null;
+    Number projectId = session.getSingleResult(query, null);
+    if (projectId != null) {
+      snapshot = new Snapshot();
+      snapshot.setResourceId(projectId.intValue());
+      snapshot.setCreatedAt(project.getAnalysisDate());
+    }
+    return snapshot;
+  }
+
+  private void log(PastSnapshot pastSnapshot) {
+    String qualifier = pastSnapshot.getQualifier();
+    // hack to avoid too many logs when the views plugin is installed
+    if (StringUtils.equals(Qualifiers.VIEW, qualifier) || StringUtils.equals(Qualifiers.SUBVIEW, qualifier)) {
+      LoggerFactory.getLogger(getClass()).debug(pastSnapshot.toString());
+    } else {
+      Logs.INFO.info(pastSnapshot.toString());
+    }
+  }
+
+  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;
+  }
+
+  public boolean isFileVariationEnabled() {
+    return configuration.getBoolean("sonar.enableFileVariation", Boolean.FALSE);
+  }
+}
index 889a9010d78086f02cbd6c02b07df7d93780e4d6..6021e9ef78c785f13829cb2efd83f7cd35b077e8 100644 (file)
@@ -27,7 +27,6 @@ import org.sonar.api.resources.Project;
 import org.sonar.api.resources.Resource;
 import org.sonar.api.resources.ResourceUtils;
 import org.sonar.batch.components.PastSnapshot;
-import org.sonar.batch.components.TimeMachineConfiguration;
 import org.sonar.core.NotDryRun;
 
 import java.util.List;
index 1fe3ec56cc1b347a23380e7119fb385a5cb83983..5299904a13b67da4151734f7781f19e1727c8d3c 100644 (file)
@@ -22,36 +22,38 @@ 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.CoreProperties;
 import org.sonar.api.batch.*;
 import org.sonar.api.measures.*;
 import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.resources.Resource;
 import org.sonar.api.resources.Scopes;
 import org.sonar.batch.components.PastMeasuresLoader;
 import org.sonar.batch.components.PastSnapshot;
-import org.sonar.batch.components.TimeMachineConfiguration;
-import org.sonar.core.NotDryRun;
 
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
-@NotDryRun
 @DependedUpon(DecoratorBarriers.END_OF_TIME_MACHINE)
 public class VariationDecorator implements Decorator {
 
   private List<PastSnapshot> projectPastSnapshots;
   private MetricFinder metricFinder;
   private PastMeasuresLoader pastMeasuresLoader;
+  private final boolean enabledFileVariation;
 
 
   public VariationDecorator(PastMeasuresLoader pastMeasuresLoader, MetricFinder metricFinder, TimeMachineConfiguration configuration) {
-    this(pastMeasuresLoader, metricFinder, configuration.getProjectPastSnapshots());
+    this(pastMeasuresLoader, metricFinder, configuration.getProjectPastSnapshots(), configuration.isFileVariationEnabled());
   }
-  public VariationDecorator(PastMeasuresLoader pastMeasuresLoader, MetricFinder metricFinder, List<PastSnapshot> projectPastSnapshots) {
+
+  VariationDecorator(PastMeasuresLoader pastMeasuresLoader, MetricFinder metricFinder, List<PastSnapshot> projectPastSnapshots, boolean enabledFileVariation) {
     this.pastMeasuresLoader = pastMeasuresLoader;
     this.projectPastSnapshots = projectPastSnapshots;
     this.metricFinder = metricFinder;
+    this.enabledFileVariation = enabledFileVariation;
   }
 
   public boolean shouldExecuteOnProject(Project project) {
@@ -64,19 +66,23 @@ public class VariationDecorator implements Decorator {
   }
 
   public void decorate(Resource resource, DecoratorContext context) {
-    if (shouldCalculateVariations(resource)) {
-      for (PastSnapshot projectPastSnapshot : projectPastSnapshots) {
-        calculateVariation(resource, context, projectPastSnapshot);
+    for (PastSnapshot projectPastSnapshot : projectPastSnapshots) {
+      if (shouldComputeVariation(projectPastSnapshot.getMode(), resource)) {
+        computeVariation(resource, context, projectPastSnapshot);
       }
     }
   }
 
-  static boolean shouldCalculateVariations(Resource resource) {
+  boolean shouldComputeVariation(String variationMode, Resource resource) {
+    if (Scopes.FILE.equals(resource.getScope()) && !Qualifiers.UNIT_TEST_FILE.equals(resource.getQualifier())) {
+      return enabledFileVariation && StringUtils.equals(variationMode, CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS);
+    }
+
     // measures on files are currently purged, so past measures are not available on files
     return StringUtils.equals(Scopes.PROJECT, resource.getScope()) || StringUtils.equals(Scopes.DIRECTORY, resource.getScope());
   }
 
-  private void calculateVariation(Resource resource, DecoratorContext context, PastSnapshot pastSnapshot) {
+  private void computeVariation(Resource resource, DecoratorContext context, PastSnapshot pastSnapshot) {
     List<Object[]> pastMeasures = pastMeasuresLoader.getPastMeasures(resource, pastSnapshot);
     compareWithPastMeasures(context, pastSnapshot.getIndex(), pastMeasures);
   }
index 22a239811b3cd3257970b8a373d83e3ffe79384c..b027004067be1c47354d8a1853dbbfb2239314db 100644 (file)
@@ -38,7 +38,6 @@ import org.sonar.api.rules.Rule;
 import org.sonar.api.rules.RulePriority;
 import org.sonar.api.rules.Violation;
 import org.sonar.batch.components.PastSnapshot;
-import org.sonar.batch.components.TimeMachineConfiguration;
 
 import java.util.Arrays;
 import java.util.Date;
index 9fd3336ab0cc601541af41fab0e2a0bc1b72bf17..5999463ec2174864956ff34d8c74e2dbe54f5dcf 100644 (file)
@@ -29,7 +29,6 @@ import org.sonar.api.measures.Measure;
 import org.sonar.api.measures.MetricFinder;
 import org.sonar.api.resources.JavaPackage;
 import org.sonar.api.resources.Project;
-import org.sonar.batch.components.TimeMachineConfiguration;
 
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
@@ -61,7 +60,9 @@ public class TendencyDecoratorTest {
   }
 
   private TimeMachineConfiguration newConf() {
-    return new TimeMachineConfiguration(new PropertiesConfiguration());
+    TimeMachineConfiguration configuration = mock(TimeMachineConfiguration.class);
+    when(configuration.getTendencyPeriodInDays()).thenReturn(30);
+    return configuration;
   }
 
   @Test
index afc51cf8969e7ac81729f6d50c245c9abfd56c1d..21dcfefffecf244674b4a3d8af4e37eb450e13b5 100644 (file)
@@ -23,7 +23,6 @@ import org.junit.Test;
 import org.sonar.api.database.model.Snapshot;
 import org.sonar.api.utils.DateUtils;
 import org.sonar.batch.components.PastSnapshot;
-import org.sonar.batch.components.TimeMachineConfiguration;
 import org.sonar.jpa.test.AbstractDbUnitTestCase;
 
 import java.text.ParseException;
index f1444bd6accfddeb141bfe48674f103bad751ffa..dda4f1a77b86df4879616932b53c9bc00db55945 100644 (file)
 package org.sonar.plugins.core.timemachine;
 
 import org.apache.commons.configuration.PropertiesConfiguration;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
 import org.junit.Test;
 import org.sonar.api.CoreProperties;
 import org.sonar.api.database.model.Snapshot;
-import org.sonar.api.utils.DateUtils;
-import org.sonar.batch.components.PastSnapshot;
+import org.sonar.api.resources.Project;
 import org.sonar.batch.components.PastSnapshotFinder;
-import org.sonar.batch.components.TimeMachineConfiguration;
 import org.sonar.jpa.test.AbstractDbUnitTestCase;
 
-import java.text.ParseException;
-import java.util.Date;
-
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.*;
 
 public class TimeMachineConfigurationTest extends AbstractDbUnitTestCase {
@@ -42,32 +41,41 @@ public class TimeMachineConfigurationTest extends AbstractDbUnitTestCase {
   public void shouldSkipTendencies() {
     PropertiesConfiguration conf = new PropertiesConfiguration();
     conf.setProperty(CoreProperties.SKIP_TENDENCIES_PROPERTY, true);
-    assertThat(new TimeMachineConfiguration(conf).skipTendencies(), is(true));
+    assertThat(new TimeMachineConfiguration(getSession(), new Project("my:project"), conf, mock(PastSnapshotFinder.class)).skipTendencies(), is(true));
   }
 
   @Test
   public void shouldNotSkipTendenciesByDefault() {
     PropertiesConfiguration conf = new PropertiesConfiguration();
-    assertThat(new TimeMachineConfiguration(conf).skipTendencies(), is(false));
+    assertThat(new TimeMachineConfiguration(getSession(), new Project("my:project"), conf, mock(PastSnapshotFinder.class)).skipTendencies(), is(false));
   }
 
   @Test
-  public void shouldInitPastSnapshots() throws ParseException {
+  public void shouldInitPastSnapshots() {
+    setupData("shared");
     PropertiesConfiguration conf = new PropertiesConfiguration();
     PastSnapshotFinder pastSnapshotFinder = mock(PastSnapshotFinder.class);
-    when(pastSnapshotFinder.find(null, conf, 1)).thenReturn(new PastSnapshot("days", new Date(), newSnapshot("2010-10-15")));
-    when(pastSnapshotFinder.find(null, conf, 3)).thenReturn(new PastSnapshot("days", new Date(), newSnapshot("2010-10-13")));
 
-    TimeMachineConfiguration timeMachineConfiguration = new TimeMachineConfiguration(conf, pastSnapshotFinder, null);
+    new TimeMachineConfiguration(getSession(), new Project("my:project"), conf, pastSnapshotFinder);
 
-    verify(pastSnapshotFinder).find(null, conf, 1);
-    verify(pastSnapshotFinder).find(null, conf, 2);
-    verify(pastSnapshotFinder).find(null, conf, 3);
+    verify(pastSnapshotFinder).find(argThat(new BaseMatcher<Snapshot>() {
+      public boolean matches(Object o) {
+        return ((Snapshot) o).getResourceId() == 2 /* see database in shared.xml */;
+      }
 
-    assertThat(timeMachineConfiguration.getProjectPastSnapshots().size(), is(2));
+      public void describeTo(Description description) {
+      }
+    }), eq(conf), eq(1));
   }
 
-  private Snapshot newSnapshot(String date) throws ParseException {
-    return new Snapshot().setCreatedAt(DateUtils.parseDate(date));
+  @Test
+  public void shouldNotInitPastSnapshotsIfFirstAnalysis() {
+    setupData("shared");
+    PropertiesConfiguration conf = new PropertiesConfiguration();
+    PastSnapshotFinder pastSnapshotFinder = mock(PastSnapshotFinder.class);
+
+    new TimeMachineConfiguration(getSession(), new Project("new:project"), conf, pastSnapshotFinder);
+
+    verifyZeroInteractions(pastSnapshotFinder);
   }
 }
index ad4fd12459efc956745e3dfab72edae0f9f92e92..27c2fff471b7666a26f3b1fffdb7925218ba3cdd 100644 (file)
@@ -21,9 +21,9 @@ package org.sonar.plugins.core.timemachine;
 
 import org.junit.Test;
 import org.mockito.Matchers;
+import org.sonar.api.CoreProperties;
 import org.sonar.api.batch.DecoratorContext;
 import org.sonar.api.database.model.MeasureModel;
-import org.sonar.api.database.model.Snapshot;
 import org.sonar.api.measures.Measure;
 import org.sonar.api.measures.MeasuresFilter;
 import org.sonar.api.measures.Metric;
@@ -50,14 +50,41 @@ public class VariationDecoratorTest extends AbstractDbUnitTestCase {
   public static final Metric COVERAGE = new Metric("coverage").setId(COVERAGE_ID);
 
   @Test
-  public void shouldNotCalculateVariationsOnFiles() {
-    assertThat(VariationDecorator.shouldCalculateVariations(new Project("foo")), is(true));
-    assertThat(VariationDecorator.shouldCalculateVariations(new JavaPackage("org.foo")), is(true));
-    assertThat(VariationDecorator.shouldCalculateVariations(new Directory("org/foo")), is(true));
-
-    assertThat(VariationDecorator.shouldCalculateVariations(new JavaFile("org.foo.Bar")), is(false));
-    assertThat(VariationDecorator.shouldCalculateVariations(new JavaFile("org.foo.Bar", true)), is(false));
-    assertThat(VariationDecorator.shouldCalculateVariations(new File("org/foo/Bar.php")), is(false));
+  public void shouldComputeVariations() {
+    TimeMachineConfiguration conf = mock(TimeMachineConfiguration.class);
+    when(conf.isFileVariationEnabled()).thenReturn(false);
+    VariationDecorator decorator = new VariationDecorator(mock(PastMeasuresLoader.class), mock(MetricFinder.class), conf);
+
+    assertThat(decorator.shouldComputeVariation(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS, new Project("foo")), is(true));
+    assertThat(decorator.shouldComputeVariation(CoreProperties.TIMEMACHINE_MODE_DATE, new Project("foo")), is(true));
+  }
+
+  @Test
+  public void shouldNotComputeFileVariations() {
+    TimeMachineConfiguration conf = mock(TimeMachineConfiguration.class);
+    when(conf.isFileVariationEnabled()).thenReturn(false);
+    VariationDecorator decorator = new VariationDecorator(mock(PastMeasuresLoader.class), mock(MetricFinder.class), conf);
+
+    assertThat(decorator.shouldComputeVariation(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS, new JavaFile("org.foo.Bar")), is(false));
+    assertThat(decorator.shouldComputeVariation(CoreProperties.TIMEMACHINE_MODE_DATE, new JavaFile("org.foo.Bar")), is(false));
+    assertThat(decorator.shouldComputeVariation(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS, new File("org/foo/Bar.php")), is(false));
+    assertThat(decorator.shouldComputeVariation(CoreProperties.TIMEMACHINE_MODE_DATE, new File("org/foo/Bar.php")), is(false));
+  }
+
+  @Test
+  public void shouldComputeFileVariationsIfExplictlyEnabled() {
+    TimeMachineConfiguration conf = mock(TimeMachineConfiguration.class);
+    when(conf.isFileVariationEnabled()).thenReturn(true);
+    VariationDecorator decorator = new VariationDecorator(mock(PastMeasuresLoader.class), mock(MetricFinder.class), conf);
+
+    // only for variation with reference analysis
+    assertThat(decorator.shouldComputeVariation(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS, new JavaFile("org.foo.Bar")), is(true));
+    assertThat(decorator.shouldComputeVariation(CoreProperties.TIMEMACHINE_MODE_DATE, new JavaFile("org.foo.Bar")), is(false));
+    assertThat(decorator.shouldComputeVariation(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS, new File("org/foo/Bar.php")), is(true));
+    assertThat(decorator.shouldComputeVariation(CoreProperties.TIMEMACHINE_MODE_DATE, new File("org/foo/Bar.php")), is(false));
+
+    // no side-effect on other resources
+    assertThat(decorator.shouldComputeVariation(CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS, new Project("foo")), is(true));
   }
 
   @Test
@@ -83,8 +110,7 @@ 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), false);
     decorator.decorate(javaPackage, context);
 
     // context updated for each variation : 2 times for ncloc and 1 time for coverage
diff --git a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/TimeMachineConfigurationTest/shared.xml b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/TimeMachineConfigurationTest/shared.xml
new file mode 100644 (file)
index 0000000..5e53bd4
--- /dev/null
@@ -0,0 +1,12 @@
+<dataset>
+
+  <projects long_name="[null]" id="1" scope="PRJ" kee="my:project" qualifier="LIB" name="my project as lib"
+            root_id="[null]" description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+  <projects long_name="[null]" id="2" scope="PRJ" kee="my:project" qualifier="TRK" name="my project"
+            root_id="[null]" description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+  <projects long_name="[null]" id="3" scope="DIR" kee="my:project:path/to/dir" qualifier="TRK" name="my dir"
+            root_id="2" description="[null]" enabled="true" profile_id="[null]" language="java" copy_resource_id="[null]"/>
+
+</dataset>
index 59d3dff04f9bb9d17e1cc1a549a3fad51f11cde1..ee7c07ebb3f3beed291863f02692edc8c4499503 100644 (file)
@@ -31,7 +31,6 @@ import org.sonar.api.resources.ProjectFileSystem;
 import org.sonar.api.rules.DefaultRulesManager;
 import org.sonar.api.utils.SonarException;
 import org.sonar.batch.*;
-import org.sonar.batch.components.TimeMachineConfiguration;
 import org.sonar.batch.events.EventBus;
 import org.sonar.batch.index.DefaultIndex;
 import org.sonar.batch.index.DefaultResourcePersister;
@@ -78,7 +77,6 @@ public class ProjectModule extends Module {
     if (!dryRun) {
       // the Snapshot component will be removed when asynchronous measures are improved (required for AsynchronousMeasureSensor)
       addComponent(getComponent(DefaultResourcePersister.class).getSnapshot(project));
-      addComponent(TimeMachineConfiguration.class);
     }
     addComponent(org.sonar.api.database.daos.MeasuresDao.class);
     addComponent(ProfilesDao.class);
index 11683350a8b53c9a01f256378d42701e0a4c4138..c4dd2a56dc05857bf4877848ff2527400df1dd4b 100644 (file)
@@ -23,9 +23,11 @@ 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.ResourceModel;
 import org.sonar.api.database.model.Snapshot;
 import org.sonar.api.measures.Metric;
 import org.sonar.api.measures.MetricFinder;
+import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.resources.Resource;
 
 import java.util.Collection;
@@ -57,28 +59,25 @@ public class PastMeasuresLoader implements BatchExtension {
   }
 
   public List<Object[]> getPastMeasures(Resource resource, PastSnapshot projectPastSnapshot) {
-    if (isPersisted(resource) && projectPastSnapshot != null && projectPastSnapshot.getProjectSnapshot()!=null) {
-      return getPastMeasures(resource.getId(), projectPastSnapshot.getProjectSnapshot());
+    if (projectPastSnapshot != null && projectPastSnapshot.getProjectSnapshot()!=null) {
+      return getPastMeasures(resource.getEffectiveKey(), projectPastSnapshot.getProjectSnapshot());
     }
     return Collections.emptyList();
   }
 
-  public List<Object[]> getPastMeasures(int resourceId, Snapshot projectSnapshot) {
+  public List<Object[]> getPastMeasures(String resourceKey, Snapshot projectPastSnapshot) {
     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";
+        " and (s.root_snapshot_id=:rootSnapshotId or s.id=:rootSnapshotId) and s.status=:status and s.project_id=(select p.id from projects p where p.kee=:resourceKey and p.qualifier<>:lib)";
     return session.createNativeQuery(sql)
         .setParameter("metricIds", metricByIds.keySet())
-        .setParameter("rootSnapshotId", ObjectUtils.defaultIfNull(projectSnapshot.getRootId(), projectSnapshot.getId()))
-        .setParameter("resourceId", resourceId)
+        .setParameter("rootSnapshotId", ObjectUtils.defaultIfNull(projectPastSnapshot.getRootId(), projectPastSnapshot.getId()))
+        .setParameter("resourceKey", resourceKey)
+        .setParameter("lib", Qualifiers.LIBRARY)
         .setParameter("status", Snapshot.STATUS_PROCESSED)
         .getResultList();
   }
 
-  private boolean isPersisted(Resource resource) {
-    return resource.getId() != null;
-  }
-
   public static int getMetricId(Object[] row) {
     // can be BigDecimal on Oracle
     return ((Number)row[0]).intValue();
index 843304af53b3bec557bd3b860becd8c99ee501d7..881559f14ed0bd8b4129e052f55914f7772de796 100644 (file)
@@ -99,14 +99,14 @@ public class PastSnapshot {
 
   @Override
   public String toString() {
-    if (StringUtils.equals(mode, PastSnapshotFinderByVersion.MODE)) {
+    if (StringUtils.equals(mode, CoreProperties.TIMEMACHINE_MODE_VERSION)) {
       String label = String.format("Compare to version %s", modeParameter);
       if (getTargetDate() != null) {
         label += String.format(" (%s)", DateUtils.formatDate(getTargetDate()));
       }
       return label;
     }
-    if (StringUtils.equals(mode, PastSnapshotFinderByDays.MODE)) {
+    if (StringUtils.equals(mode, CoreProperties.TIMEMACHINE_MODE_DAYS)) {
       String label = String.format("Compare over %s days (%s", modeParameter, DateUtils.formatDate(getTargetDate()));
       if (isRelatedToSnapshot()) {
         label += ", analysis of " + getDate();
@@ -121,7 +121,7 @@ public class PastSnapshot {
       }
       return label;
     }
-    if (StringUtils.equals(mode, PastSnapshotFinderByDate.MODE)) {
+    if (StringUtils.equals(mode, CoreProperties.TIMEMACHINE_MODE_DATE)) {
       String label = "Compare to date " + DateUtils.formatDate(getTargetDate());
       if (isRelatedToSnapshot()) {
         label += String.format(" (analysis of %s)", DateUtils.formatDate(getDate()));
index dd59bb2009d984ad8da329a7f2f63b5e706ed97e..ec0ef5d2191642e1e60ff8a1638ecfc66ceb6035 100644 (file)
@@ -69,6 +69,10 @@ public class PastSnapshotFinder implements BatchExtension {
     return conf.getString(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + index, defaultValue);
   }
 
+  public PastSnapshot findPreviousAnalysis(Snapshot projectSnapshot) {
+    return finderByPreviousAnalysis.findByPreviousAnalysis(projectSnapshot);
+  }
+
   public PastSnapshot find(Snapshot projectSnapshot, int index, String property) {
     if (StringUtils.isBlank(property)) {
       return null;
index 4340ac6c4b3f32b71bba17f3c0625a6a4c575064..d605b7cabf921148d41b36c1de7d972dae25687a 100644 (file)
@@ -20,6 +20,7 @@
 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 org.sonar.api.resources.Qualifiers;
@@ -31,8 +32,6 @@ import java.util.List;
 
 public class PastSnapshotFinderByDate implements BatchExtension {
 
-  public static final String MODE = "date";
-
   private DatabaseSession session;
 
   public PastSnapshotFinderByDate(DatabaseSession session) {
@@ -45,7 +44,7 @@ public class PastSnapshotFinderByDate implements BatchExtension {
       snapshot = findSnapshot(projectSnapshot, date);
     }
     SimpleDateFormat format = new SimpleDateFormat(DateUtils.DATE_FORMAT);
-    return new PastSnapshot(MODE, date, snapshot).setModeParameter(format.format(date));
+    return new PastSnapshot(CoreProperties.TIMEMACHINE_MODE_DATE, date, snapshot).setModeParameter(format.format(date));
   }
 
 
index b3763107e9c3c2770213d3ae6d57ffce792aa15b..548d339e31a5d9b2e61279a98ebb857f1331d085 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.batch.components;
 
 import org.apache.commons.lang.time.DateUtils;
 import org.sonar.api.BatchExtension;
+import org.sonar.api.CoreProperties;
 import org.sonar.api.database.DatabaseSession;
 import org.sonar.api.database.model.Snapshot;
 import org.sonar.api.resources.Qualifiers;
@@ -30,8 +31,6 @@ import java.util.List;
 
 public class PastSnapshotFinderByDays implements BatchExtension {
 
-  public static final String MODE = "days";
-
   private DatabaseSession session;
 
   public PastSnapshotFinderByDays(DatabaseSession session) {
@@ -49,7 +48,7 @@ public class PastSnapshotFinderByDays implements BatchExtension {
         .getResultList();
 
     Snapshot snapshot = getNearestToTarget(snapshots, targetDate);
-    return new PastSnapshot(MODE, targetDate, snapshot).setModeParameter(String.valueOf(days));
+    return new PastSnapshot(CoreProperties.TIMEMACHINE_MODE_DAYS, targetDate, snapshot).setModeParameter(String.valueOf(days));
   }
 
   static Snapshot getNearestToTarget(List<Snapshot> snapshots, Date currentDate, int distanceInDays) {
index c4cc272c5db413c8dea41ed6d3b3f5da8352d81e..c5de66950d664cbd9ea3a747a46e28a50993ebd8 100644 (file)
@@ -20,6 +20,7 @@
 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 org.sonar.api.resources.Qualifiers;
@@ -29,8 +30,6 @@ import java.util.List;
 
 public class PastSnapshotFinderByVersion implements BatchExtension {
 
-  public static final String MODE = "version";
-
   private DatabaseSession session;
 
   public PastSnapshotFinderByVersion(DatabaseSession session) {
@@ -48,11 +47,11 @@ public class PastSnapshotFinderByVersion implements BatchExtension {
         .getResultList();
 
     if (snapshots.isEmpty()) {
-      return new PastSnapshot(MODE);
+      return new PastSnapshot(CoreProperties.TIMEMACHINE_MODE_VERSION);
     }
     Snapshot snapshot = snapshots.get(0);
     Date targetDate = snapshot.getCreatedAt();
-    return new PastSnapshot(MODE, targetDate, snapshot).setModeParameter(version);
+    return new PastSnapshot(CoreProperties.TIMEMACHINE_MODE_VERSION, targetDate, snapshot).setModeParameter(version);
   }
 
 }
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
deleted file mode 100644 (file)
index ff317f2..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2008-2011 SonarSource
- * 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.apache.commons.lang.StringUtils;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.BatchExtension;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.database.model.Snapshot;
-import org.sonar.api.resources.Qualifiers;
-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 pastSnapshot = pastSnapshotFinder.find(projectSnapshot, configuration, index);
-      if (pastSnapshot != null) {
-        log(pastSnapshot);
-        projectPastSnapshots.add(pastSnapshot);
-      }
-    }
-  }
-
-  private void log(PastSnapshot pastSnapshot) {
-    String qualifier = pastSnapshot.getQualifier();
-    // hack to avoid too many logs when the views plugin is installed
-    if (StringUtils.equals(Qualifiers.VIEW, qualifier) || StringUtils.equals(Qualifiers.SUBVIEW, qualifier)) {
-      LoggerFactory.getLogger(getClass()).debug(pastSnapshot.toString());
-    } else {
-      Logs.INFO.info(pastSnapshot.toString());
-    }
-  }
-
-  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;
-  }
-}
index 74e19b28ddf293385d6aac0e04527970213fb9ac..40c253e78b36e263b7f474ff90d601448ae78839 100644 (file)
 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;
@@ -38,8 +36,8 @@ 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;
+  private static final String PROJECT_KEY = "project";
+  private static final String FILE_KEY = "project:org.foo.Bar";
 
   @Test
   public void shouldGetPastResourceMeasures() {
@@ -49,7 +47,7 @@ public class PastMeasuresLoaderTest extends AbstractDbUnitTestCase {
     Snapshot projectSnapshot = getSession().getSingleResult(Snapshot.class, "id", PROJECT_SNAPSHOT_ID);
 
     PastMeasuresLoader loader = new PastMeasuresLoader(getSession(), metrics);
-    List<Object[]> measures = loader.getPastMeasures(FILE_ID, projectSnapshot);
+    List<Object[]> measures = loader.getPastMeasures(FILE_KEY, projectSnapshot);
     assertThat(measures.size(), is(2));
 
     Object[] pastMeasure = measures.get(0);
@@ -71,7 +69,7 @@ public class PastMeasuresLoaderTest extends AbstractDbUnitTestCase {
     Snapshot projectSnapshot = getSession().getSingleResult(Snapshot.class, "id", PROJECT_SNAPSHOT_ID);
 
     PastMeasuresLoader loader = new PastMeasuresLoader(getSession(), metrics);
-    List<Object[]> measures = loader.getPastMeasures(PROJECT_ID, projectSnapshot);
+    List<Object[]> measures = loader.getPastMeasures(PROJECT_KEY, projectSnapshot);
     assertThat(measures.size(), is(2));
 
     Object[] pastMeasure = measures.get(0);
index 8bd305e85e2aab5bb327ce3b287e62548a08efb2..d403bd0b7a05c1359028091988de2d4dc2922902 100644 (file)
@@ -33,37 +33,37 @@ public class PastSnapshotTest {
 
   @Test
   public void testToStringForVersion() {
-    PastSnapshot pastSnapshot = new PastSnapshot(PastSnapshotFinderByVersion.MODE, new Date()).setModeParameter("2.3");
+    PastSnapshot pastSnapshot = new PastSnapshot(CoreProperties.TIMEMACHINE_MODE_VERSION, new Date()).setModeParameter("2.3");
     assertThat(pastSnapshot.toString(), startsWith("Compare to version 2.3"));
   }
 
   @Test
   public void testToStringForVersionWithoutDate() {
-    PastSnapshot pastSnapshot = new PastSnapshot(PastSnapshotFinderByVersion.MODE).setModeParameter("2.3");
+    PastSnapshot pastSnapshot = new PastSnapshot(CoreProperties.TIMEMACHINE_MODE_VERSION).setModeParameter("2.3");
     assertThat(pastSnapshot.toString(), equalTo("Compare to version 2.3"));
   }
 
   @Test
   public void testToStringForNumberOfDays() {
-    PastSnapshot pastSnapshot = new PastSnapshot(PastSnapshotFinderByDays.MODE, new Date()).setModeParameter("30");
+    PastSnapshot pastSnapshot = new PastSnapshot(CoreProperties.TIMEMACHINE_MODE_DAYS, new Date()).setModeParameter("30");
     assertThat(pastSnapshot.toString(), startsWith("Compare over 30 days ("));
   }
 
   @Test
   public void testToStringForNumberOfDaysWithSnapshot() {
-    PastSnapshot pastSnapshot = new PastSnapshot(PastSnapshotFinderByDays.MODE, new Date(), new Snapshot().setCreatedAt(new Date())).setModeParameter("30");
+    PastSnapshot pastSnapshot = new PastSnapshot(CoreProperties.TIMEMACHINE_MODE_DAYS, new Date(), new Snapshot().setCreatedAt(new Date())).setModeParameter("30");
     assertThat(pastSnapshot.toString(), startsWith("Compare over 30 days ("));
   }
 
   @Test
   public void testToStringForDate() {
-    PastSnapshot pastSnapshot = new PastSnapshot(PastSnapshotFinderByDate.MODE, new Date());
+    PastSnapshot pastSnapshot = new PastSnapshot(CoreProperties.TIMEMACHINE_MODE_DATE, new Date());
     assertThat(pastSnapshot.toString(), startsWith("Compare to date "));
   }
 
   @Test
   public void testToStringForDateWithSnapshot() {
-    PastSnapshot pastSnapshot = new PastSnapshot(PastSnapshotFinderByDate.MODE, new Date(), new Snapshot().setCreatedAt(new Date()));
+    PastSnapshot pastSnapshot = new PastSnapshot(CoreProperties.TIMEMACHINE_MODE_DATE, new Date(), new Snapshot().setCreatedAt(new Date()));
     assertThat(pastSnapshot.toString(), startsWith("Compare to date "));
   }
 
index 49fa07f4c954200d5cd3a64deb11c88ff965b3a3..e19a5a3f13b9344d0119bedef3197abe9f22319b 100644 (file)
@@ -159,6 +159,9 @@ public interface CoreProperties {
   /* Time machine periods */
   String TIMEMACHINE_PERIOD_PREFIX = "sonar.timemachine.period";
   String TIMEMACHINE_MODE_PREVIOUS_ANALYSIS = "previous_analysis";
+  String TIMEMACHINE_MODE_DATE = "date";
+  String TIMEMACHINE_MODE_VERSION = "version";
+  String TIMEMACHINE_MODE_DAYS = "days";
   String TIMEMACHINE_DEFAULT_PERIOD_1 = TIMEMACHINE_MODE_PREVIOUS_ANALYSIS;
   String TIMEMACHINE_DEFAULT_PERIOD_2 = "5";
   String TIMEMACHINE_DEFAULT_PERIOD_3 = "30";
index a689a25bb53d1a42703e2ff34d50e7151e034daa..dda4c228c5283adc86c4112349c7c58975fd4977 100644 (file)
@@ -111,6 +111,7 @@ public class Project extends Resource {
 
   public Project(String key) {
     setKey(key);
+    setEffectiveKey(key);
   }
 
   public Project(String key, String branch, String name) {
@@ -121,6 +122,7 @@ public class Project extends Resource {
       setKey(key);
       this.name = name;
     }
+    setEffectiveKey(getKey());
     this.branch = branch;
   }
 
@@ -305,6 +307,8 @@ public class Project extends Resource {
     return parent;
   }
 
+
+
   /**
    * For internal use only.
    */
index d52bbd6a74620109d76c17e756433101e1293036..8df12c566730ba42f80fda27fde248530b88dff7 100644 (file)
@@ -37,6 +37,11 @@ public class ProjectTest {
     assertEquals(project1.hashCode(), project2.hashCode());
   }
 
+  @Test
+  public void effectiveKeyShouldEqualKey() {
+    assertThat(new Project("my:project").getEffectiveKey(), is("my:project"));
+  }
+
   @Test
   public void createFromMavenIds() {
     Project project = Project.createFromMavenIds("my", "artifact");