]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5931 Add a tester class for SensorContext
authorJulien HENRY <julien.henry@sonarsource.com>
Fri, 20 Feb 2015 13:57:39 +0000 (14:57 +0100)
committerJulien HENRY <julien.henry@sonarsource.com>
Fri, 20 Feb 2015 14:05:18 +0000 (15:05 +0100)
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/lang/MeasureSensorTest.java
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/lang/SyntaxHighlightingSensorTest.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java [new file with mode: 0644]
sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java [new file with mode: 0644]

index 92169bdf42b3f80c563b2b8fe80e236d91e1e2f9..de18a8b92647ecc28b8f768e36c4d414d04c526c 100644 (file)
  */
 package org.sonar.xoo.lang;
 
-import org.sonar.api.batch.sensor.internal.SensorStorage;
-
 import org.apache.commons.io.FileUtils;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-import org.sonar.api.batch.fs.internal.DefaultFileSystem;
 import org.sonar.api.batch.fs.internal.DefaultInputFile;
 import org.sonar.api.batch.measure.MetricFinder;
-import org.sonar.api.batch.sensor.SensorContext;
 import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor;
-import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
+import org.sonar.api.batch.sensor.internal.SensorContextTester;
 import org.sonar.api.measures.CoreMetrics;
 import org.sonar.api.measures.Metric;
 
 import java.io.File;
 import java.io.IOException;
 
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 public class MeasureSensorTest {
 
   private MeasureSensor sensor;
-  private SensorContext context = mock(SensorContext.class);
-  private DefaultFileSystem fileSystem;
+  private SensorContextTester context;
 
   @Rule
   public TemporaryFolder temp = new TemporaryFolder();
@@ -59,22 +52,13 @@ public class MeasureSensorTest {
 
   private File baseDir;
   private MetricFinder metricFinder;
-  private SensorStorage storage;
 
   @Before
   public void prepare() throws IOException {
     baseDir = temp.newFolder();
     metricFinder = mock(MetricFinder.class);
     sensor = new MeasureSensor(metricFinder);
-    fileSystem = new DefaultFileSystem(baseDir.toPath());
-    when(context.fileSystem()).thenReturn(fileSystem);
-    storage = mock(SensorStorage.class);
-    when(context.newMeasure()).then(new Answer<DefaultMeasure>() {
-      @Override
-      public DefaultMeasure answer(InvocationOnMock invocation) throws Throwable {
-        return new DefaultMeasure(storage);
-      }
-    });
+    context = SensorContextTester.create(baseDir);
   }
 
   @Test
@@ -85,7 +69,7 @@ public class MeasureSensorTest {
   @Test
   public void testNoExecutionIfNoMeasureFile() {
     DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setLanguage("xoo");
-    fileSystem.add(inputFile);
+    context.fileSystem().add(inputFile);
     sensor.execute(context);
   }
 
@@ -94,7 +78,7 @@ public class MeasureSensorTest {
     File measures = new File(baseDir, "src/foo.xoo.measures");
     FileUtils.write(measures, "ncloc:12\nbranch_coverage:5.3\nsqale_index:300\nbool:true\n\n#comment");
     DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setLanguage("xoo");
-    fileSystem.add(inputFile);
+    context.fileSystem().add(inputFile);
 
     Metric<Boolean> booleanMetric = new Metric.Builder("bool", "Bool", Metric.ValueType.BOOL)
       .create();
@@ -106,10 +90,10 @@ public class MeasureSensorTest {
 
     sensor.execute(context);
 
-    verify(storage).store(new DefaultMeasure().forMetric(CoreMetrics.NCLOC).onFile(inputFile).withValue(12));
-    verify(storage).store(new DefaultMeasure().forMetric(CoreMetrics.BRANCH_COVERAGE).onFile(inputFile).withValue(5.3));
-    verify(storage).store(new DefaultMeasure().forMetric(CoreMetrics.TECHNICAL_DEBT).onFile(inputFile).withValue(300L));
-    verify(storage).store(new DefaultMeasure().forMetric(booleanMetric).onFile(inputFile).withValue(true));
+    assertThat(context.measure("foo:src/foo.xoo", CoreMetrics.NCLOC).value()).isEqualTo(12);
+    assertThat(context.measure("foo:src/foo.xoo", CoreMetrics.BRANCH_COVERAGE).value()).isEqualTo(5.3);
+    assertThat(context.measure("foo:src/foo.xoo", CoreMetrics.TECHNICAL_DEBT).value()).isEqualTo(300L);
+    assertThat(context.measure("foo:src/foo.xoo", booleanMetric).value()).isTrue();
   }
 
   @Test
@@ -117,7 +101,7 @@ public class MeasureSensorTest {
     File measures = new File(baseDir, "src/foo.xoo.measures");
     FileUtils.write(measures, "unknow:12\n\n#comment");
     DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setLanguage("xoo");
-    fileSystem.add(inputFile);
+    context.fileSystem().add(inputFile);
 
     thrown.expect(IllegalStateException.class);
 
index 4bd8aa2da61a0125023a37a9e480a430dcec8bc4..9b9a7baca053088f564b680354e494634f394293 100644 (file)
@@ -24,27 +24,20 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
-import org.sonar.api.batch.fs.InputFile;
-import org.sonar.api.batch.fs.internal.DefaultFileSystem;
 import org.sonar.api.batch.fs.internal.DefaultInputFile;
-import org.sonar.api.batch.sensor.SensorContext;
-import org.sonar.api.batch.sensor.highlighting.NewHighlighting;
 import org.sonar.api.batch.sensor.highlighting.TypeOfText;
 import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor;
+import org.sonar.api.batch.sensor.internal.SensorContextTester;
 
 import java.io.File;
 import java.io.IOException;
 
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.assertj.core.api.Assertions.assertThat;
 
 public class SyntaxHighlightingSensorTest {
 
   private SyntaxHighlightingSensor sensor;
-  private SensorContext context = mock(SensorContext.class);
-  private DefaultFileSystem fileSystem;
+  private SensorContextTester context;
 
   @Rule
   public TemporaryFolder temp = new TemporaryFolder();
@@ -54,8 +47,7 @@ public class SyntaxHighlightingSensorTest {
   public void prepare() throws IOException {
     baseDir = temp.newFolder();
     sensor = new SyntaxHighlightingSensor();
-    fileSystem = new DefaultFileSystem(baseDir.toPath());
-    when(context.fileSystem()).thenReturn(fileSystem);
+    context = SensorContextTester.create(baseDir);
   }
 
   @Test
@@ -66,7 +58,7 @@ public class SyntaxHighlightingSensorTest {
   @Test
   public void testNoExecutionIfNoSyntaxFile() {
     DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setLanguage("xoo");
-    fileSystem.add(inputFile);
+    context.fileSystem().add(inputFile);
     sensor.execute(context);
   }
 
@@ -75,15 +67,11 @@ public class SyntaxHighlightingSensorTest {
     File symbol = new File(baseDir, "src/foo.xoo.highlighting");
     FileUtils.write(symbol, "1:4:k\n12:15:cppd\n\n#comment");
     DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setLanguage("xoo");
-    fileSystem.add(inputFile);
-    NewHighlighting builder = mock(NewHighlighting.class);
-    when(context.newHighlighting()).thenReturn(builder);
-    when(builder.onFile(any(InputFile.class))).thenReturn(builder);
+    context.fileSystem().add(inputFile);
 
     sensor.execute(context);
 
-    verify(builder).highlight(1, 4, TypeOfText.KEYWORD);
-    verify(builder).highlight(12, 15, TypeOfText.CPP_DOC);
-    verify(builder).save();
+    assertThat(context.highlightingTypeFor("foo:src/foo.xoo", 2)).containsOnly(TypeOfText.KEYWORD);
+    assertThat(context.highlightingTypeFor("foo:src/foo.xoo", 13)).containsOnly(TypeOfText.CPP_DOC);
   }
 }
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java
new file mode 100644 (file)
index 0000000..1a41cce
--- /dev/null
@@ -0,0 +1,303 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.api.batch.sensor.internal;
+
+import com.google.common.annotations.Beta;
+import org.sonar.api.batch.AnalysisMode;
+import org.sonar.api.batch.fs.InputDir;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.InputPath;
+import org.sonar.api.batch.fs.internal.DefaultFileSystem;
+import org.sonar.api.batch.fs.internal.DefaultInputDir;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.rule.ActiveRules;
+import org.sonar.api.batch.rule.internal.ActiveRulesBuilder;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.dependency.Dependency;
+import org.sonar.api.batch.sensor.dependency.NewDependency;
+import org.sonar.api.batch.sensor.dependency.internal.DefaultDependency;
+import org.sonar.api.batch.sensor.duplication.Duplication;
+import org.sonar.api.batch.sensor.duplication.NewDuplication;
+import org.sonar.api.batch.sensor.duplication.internal.DefaultDuplication;
+import org.sonar.api.batch.sensor.highlighting.NewHighlighting;
+import org.sonar.api.batch.sensor.highlighting.TypeOfText;
+import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting;
+import org.sonar.api.batch.sensor.highlighting.internal.SyntaxHighlightingRule;
+import org.sonar.api.batch.sensor.issue.Issue;
+import org.sonar.api.batch.sensor.issue.NewIssue;
+import org.sonar.api.batch.sensor.issue.internal.DefaultIssue;
+import org.sonar.api.batch.sensor.measure.Measure;
+import org.sonar.api.batch.sensor.measure.NewMeasure;
+import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
+import org.sonar.api.config.Settings;
+import org.sonar.api.measures.Metric;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Utility class to help testing {@link Sensor}
+ * @since 5.1
+ */
+@Beta
+public class SensorContextTester implements SensorContext {
+
+  private Settings settings;
+  private DefaultFileSystem fs;
+  private ActiveRules activeRules;
+  private MockAnalysisMode analysisMode;
+  private InMemorySensorStorage sensorStorage;
+
+  private SensorContextTester(File moduleBaseDir) {
+    this.settings = new Settings();
+    this.fs = new DefaultFileSystem(moduleBaseDir);
+    this.activeRules = new ActiveRulesBuilder().build();
+    this.analysisMode = new MockAnalysisMode();
+    this.sensorStorage = new InMemorySensorStorage();
+  }
+
+  public static SensorContextTester create(File moduleBaseDir) {
+    return new SensorContextTester(moduleBaseDir);
+  }
+
+  @Override
+  public Settings settings() {
+    return settings;
+  }
+
+  public void setSettings(Settings settings) {
+    this.settings = settings;
+  }
+
+  @Override
+  public DefaultFileSystem fileSystem() {
+    return fs;
+  }
+
+  public void setFileSystem(DefaultFileSystem fs) {
+    this.fs = fs;
+  }
+
+  @Override
+  public ActiveRules activeRules() {
+    return activeRules;
+  }
+
+  public void setActiveRules(ActiveRules activeRules) {
+    this.activeRules = activeRules;
+  }
+
+  @Override
+  public MockAnalysisMode analysisMode() {
+    return analysisMode;
+  }
+
+  @Override
+  public <G extends Serializable> NewMeasure<G> newMeasure() {
+    return new DefaultMeasure<>(sensorStorage);
+  }
+
+  public Collection<Measure> measures(@Nullable String componetKey) {
+    if (componetKey == null) {
+      return sensorStorage.projectMeasuresByMetric.values();
+    }
+    Map<String, Measure> measures = sensorStorage.measuresByComponentAndMetric.get(componetKey);
+    return measures != null ? measures.values() : Collections.<Measure>emptyList();
+  }
+
+  public <G extends Serializable> Measure<G> measure(String componetKey, Metric<G> metric) {
+    return measure(componetKey, metric.key());
+  }
+
+  public Measure measure(String componetKey, String metricKey) {
+    if (componetKey == null) {
+      return sensorStorage.projectMeasuresByMetric.get(metricKey);
+    }
+    Map<String, Measure> measures = sensorStorage.measuresByComponentAndMetric.get(componetKey);
+    return measures != null ? measures.get(metricKey) : null;
+  }
+
+  @Override
+  public NewIssue newIssue() {
+    return new DefaultIssue(sensorStorage);
+  }
+
+  public Collection<Issue> allIssues() {
+    List<Issue> result = new ArrayList<>();
+    result.addAll(sensorStorage.projectIssues);
+    for (String key : sensorStorage.issuesByComponent.keySet()) {
+      result.addAll(sensorStorage.issuesByComponent.get(key));
+    }
+    return result;
+  }
+
+  /**
+   * @param componentKey null for project issues
+   */
+  public Collection<Issue> issues(@Nullable String componentKey) {
+    if (componentKey != null) {
+      List<Issue> list = sensorStorage.issuesByComponent.get(componentKey);
+      return list != null ? list : Collections.<Issue>emptyList();
+    } else {
+      return sensorStorage.projectIssues;
+    }
+  }
+
+  @Override
+  public NewHighlighting newHighlighting() {
+    return new DefaultHighlighting(sensorStorage);
+  }
+
+  public List<TypeOfText> highlightingTypeFor(String componentKey, int charIndex) {
+    DefaultHighlighting syntaxHighlightingData = sensorStorage.highlightingByComponent.get(componentKey);
+    if (syntaxHighlightingData == null) {
+      return Collections.emptyList();
+    }
+    List<TypeOfText> result = new ArrayList<TypeOfText>();
+    for (SyntaxHighlightingRule sortedRule : syntaxHighlightingData.getSyntaxHighlightingRuleSet()) {
+      if (sortedRule.getStartPosition() <= charIndex && sortedRule.getEndPosition() > charIndex) {
+        result.add(sortedRule.getTextType());
+      }
+    }
+    return result;
+  }
+
+  @Override
+  public NewDuplication newDuplication() {
+    return new DefaultDuplication(sensorStorage);
+  }
+
+  public Collection<Duplication> duplications() {
+    return sensorStorage.duplications;
+  }
+
+  @Override
+  public NewDependency newDependency() {
+    return new DefaultDependency(sensorStorage);
+  }
+
+  public Collection<Dependency> dependencies() {
+    return sensorStorage.dependencies;
+  }
+
+  public static class MockAnalysisMode implements AnalysisMode {
+    private boolean isIncremental = false;
+    private boolean isPreview = false;
+
+    @Override
+    public boolean isIncremental() {
+      return isIncremental;
+    }
+
+    public void setIncremental(boolean value) {
+      this.isIncremental = value;
+    }
+
+    @Override
+    public boolean isPreview() {
+      return isPreview;
+    }
+
+    public void setPreview(boolean value) {
+      this.isPreview = value;
+    }
+  }
+
+  private static class InMemorySensorStorage implements SensorStorage {
+
+    private Map<String, Measure> projectMeasuresByMetric = new HashMap<>();
+    private Map<String, Map<String, Measure>> measuresByComponentAndMetric = new HashMap<>();
+
+    private Collection<Issue> projectIssues = new ArrayList<>();
+    private Map<String, List<Issue>> issuesByComponent = new HashMap<>();
+
+    private Map<String, DefaultHighlighting> highlightingByComponent = new HashMap<>();
+
+    private List<Duplication> duplications = new ArrayList<>();
+    private List<Dependency> dependencies = new ArrayList<>();
+
+    @Override
+    public void store(Measure measure) {
+      String key = getKey(measure.inputFile());
+      if (key == null) {
+        projectMeasuresByMetric.put(measure.metric().key(), measure);
+      } else {
+        if (!measuresByComponentAndMetric.containsKey(key)) {
+          measuresByComponentAndMetric.put(key, new HashMap<String, Measure>());
+        }
+        measuresByComponentAndMetric.get(key).put(measure.metric().key(), measure);
+      }
+    }
+
+    @Override
+    public void store(Issue issue) {
+      String key = getKey(issue.inputPath());
+      if (key == null) {
+        projectIssues.add(issue);
+      } else {
+        if (!issuesByComponent.containsKey(key)) {
+          issuesByComponent.put(key, new ArrayList<Issue>());
+        }
+        issuesByComponent.get(key).add(issue);
+      }
+    }
+
+    @Override
+    public void store(Duplication duplication) {
+      duplications.add(duplication);
+    }
+
+    @Override
+    public void store(Dependency dependency) {
+      dependencies.add(dependency);
+    }
+
+    @Override
+    public void store(DefaultHighlighting highlighting) {
+      highlightingByComponent.put(getKey(highlighting.inputFile()), highlighting);
+    }
+
+    @CheckForNull
+    private String getKey(@Nullable InputPath inputPath) {
+      if (inputPath == null) {
+        return null;
+      }
+      if (inputPath instanceof InputFile) {
+        return ((DefaultInputFile) inputPath).key();
+      }
+      if (inputPath instanceof InputDir) {
+        return ((DefaultInputDir) inputPath).key();
+      }
+      throw new IllegalStateException("Unknow component " + inputPath);
+    }
+
+  }
+
+}
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java
new file mode 100644 (file)
index 0000000..090e487
--- /dev/null
@@ -0,0 +1,158 @@
+package org.sonar.api.batch.sensor.internal;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.batch.fs.internal.DefaultFileSystem;
+import org.sonar.api.batch.fs.internal.DefaultInputDir;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.rule.ActiveRules;
+import org.sonar.api.batch.rule.internal.ActiveRulesBuilder;
+import org.sonar.api.batch.sensor.highlighting.TypeOfText;
+import org.sonar.api.config.Settings;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.rule.RuleKey;
+
+import java.io.File;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SensorContextTesterTest {
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+  private SensorContextTester tester;
+  private File baseDir;
+
+  @Before
+  public void prepare() throws Exception {
+    baseDir = temp.newFolder();
+    tester = SensorContextTester.create(baseDir);
+  }
+
+  @Test
+  public void testSettings() {
+    Settings settings = new Settings();
+    settings.setProperty("foo", "bar");
+    tester.setSettings(settings);
+    assertThat(tester.settings().getString("foo")).isEqualTo("bar");
+  }
+
+  @Test
+  public void testActiveRules() {
+    ActiveRules activeRules = new ActiveRulesBuilder().create(RuleKey.of("repo", "rule")).activate().build();
+    tester.setActiveRules(activeRules);
+    assertThat(tester.activeRules().findAll()).hasSize(1);
+  }
+
+  @Test
+  public void testFs() throws Exception {
+    DefaultFileSystem fs = new DefaultFileSystem(temp.newFolder());
+    tester.setFileSystem(fs);
+    assertThat(tester.fileSystem().baseDir()).isNotEqualTo(baseDir);
+  }
+
+  @Test
+  public void testAnalysisMode() {
+    assertThat(tester.analysisMode().isIncremental()).isFalse();
+    assertThat(tester.analysisMode().isPreview()).isFalse();
+    tester.analysisMode().setIncremental(true);
+    assertThat(tester.analysisMode().isIncremental()).isTrue();
+    tester.analysisMode().setPreview(true);
+    assertThat(tester.analysisMode().isPreview()).isTrue();
+  }
+
+  @Test
+  public void testIssues() {
+    assertThat(tester.issues("foo:src/Foo.java")).isEmpty();
+    assertThat(tester.allIssues()).isEmpty();
+    tester.newIssue()
+      .onFile(new DefaultInputFile("foo", "src/Foo.java"))
+      .forRule(RuleKey.of("repo", "rule"))
+      .atLine(1)
+      .save();
+    tester.newIssue()
+      .onFile(new DefaultInputFile("foo", "src/Foo.java"))
+      .forRule(RuleKey.of("repo", "rule"))
+      .atLine(3)
+      .save();
+    assertThat(tester.issues("foo:src/Foo.java")).hasSize(2);
+    assertThat(tester.allIssues()).hasSize(2);
+    tester.newIssue()
+      .onDir(new DefaultInputDir("foo", "src"))
+      .forRule(RuleKey.of("repo", "rule"))
+      .save();
+    assertThat(tester.issues("foo:src")).hasSize(1);
+    assertThat(tester.allIssues()).hasSize(3);
+    tester.newIssue()
+      .onProject()
+      .forRule(RuleKey.of("repo", "rule"))
+      .save();
+    assertThat(tester.issues(null)).hasSize(1);
+    assertThat(tester.allIssues()).hasSize(4);
+  }
+
+  @Test
+  public void testMeasures() {
+    assertThat(tester.measures("foo:src/Foo.java")).isEmpty();
+    assertThat(tester.measure("foo:src/Foo.java", "ncloc")).isNull();
+    tester.<Integer>newMeasure()
+      .onFile(new DefaultInputFile("foo", "src/Foo.java"))
+      .forMetric(CoreMetrics.NCLOC)
+      .withValue(2)
+      .save();
+    assertThat(tester.measures("foo:src/Foo.java")).hasSize(1);
+    assertThat(tester.measure("foo:src/Foo.java", "ncloc")).isNotNull();
+    tester.<Integer>newMeasure()
+      .onFile(new DefaultInputFile("foo", "src/Foo.java"))
+      .forMetric(CoreMetrics.LINES)
+      .withValue(4)
+      .save();
+    assertThat(tester.measures("foo:src/Foo.java")).hasSize(2);
+    assertThat(tester.measure("foo:src/Foo.java", "ncloc")).isNotNull();
+    assertThat(tester.measure("foo:src/Foo.java", "lines")).isNotNull();
+    tester.<Integer>newMeasure()
+      .onProject()
+      .forMetric(CoreMetrics.DIRECTORIES)
+      .withValue(4)
+      .save();
+    assertThat(tester.measures(null)).hasSize(1);
+    assertThat(tester.measure(null, "directories")).isNotNull();
+  }
+
+  @Test
+  public void testHighlighting() {
+    assertThat(tester.highlightingTypeFor("foo:src/Foo.java", 3)).isEmpty();
+    tester.newHighlighting()
+      .onFile(new DefaultInputFile("foo", "src/Foo.java"))
+      .highlight(0, 4, TypeOfText.ANNOTATION)
+      .highlight(8, 10, TypeOfText.CONSTANT)
+      .highlight(9, 10, TypeOfText.COMMENT)
+      .save();
+    assertThat(tester.highlightingTypeFor("foo:src/Foo.java", 3)).containsExactly(TypeOfText.ANNOTATION);
+    assertThat(tester.highlightingTypeFor("foo:src/Foo.java", 9)).containsExactly(TypeOfText.CONSTANT, TypeOfText.COMMENT);
+  }
+
+  @Test
+  public void testDuplication() {
+    assertThat(tester.duplications()).isEmpty();
+    tester.newDuplication()
+      .originBlock(new DefaultInputFile("foo", "src/Foo.java").setLines(40), 1, 30)
+      .isDuplicatedBy(new DefaultInputFile("foo", "src/Foo2.java").setLines(40), 3, 33)
+      .isDuplicatedBy(new DefaultInputFile("foo", "src/Foo3.java").setLines(40), 4, 34)
+      .save();
+    assertThat(tester.duplications()).hasSize(1);
+  }
+
+  @Test
+  public void testDependencies() {
+    assertThat(tester.dependencies()).isEmpty();
+    tester.newDependency()
+      .from(new DefaultInputFile("foo", "src/Foo.java"))
+      .to(new DefaultInputFile("foo", "src/Foo2.java"))
+      .weight(3)
+      .save();
+    assertThat(tester.dependencies()).hasSize(1);
+  }
+}