Browse Source

SONAR-5389 Refactor test coverage API

tags/5.0-RC1
Julien HENRY 9 years ago
parent
commit
7df58c2fc0
26 changed files with 543 additions and 229 deletions
  1. 6
    8
      plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/CoveragePerTestSensor.java
  2. 4
    4
      plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/TestCaseSensor.java
  3. 15
    10
      plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/lang/CoveragePerTestSensorTest.java
  4. 9
    9
      plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/lang/TestCaseSensorTest.java
  5. 13
    12
      sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java
  6. 10
    14
      sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdapter.java
  7. 11
    4
      sonar-batch/src/main/java/org/sonar/batch/scan2/BaseSensorContext.java
  8. 18
    18
      sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java
  9. 4
    4
      sonar-batch/src/main/java/org/sonar/batch/scan2/ProjectScanContainer.java
  10. 81
    0
      sonar-batch/src/main/java/org/sonar/batch/test/DefaultTestCaseCoverageValueCoder.java
  11. 8
    8
      sonar-batch/src/main/java/org/sonar/batch/test/DefaultTestCaseExecutionValueCoder.java
  12. 14
    15
      sonar-batch/src/main/java/org/sonar/batch/test/TestCaseCoverageCache.java
  13. 10
    10
      sonar-batch/src/main/java/org/sonar/batch/test/TestCaseExecutionCache.java
  14. 1
    0
      sonar-batch/src/test/resources/mediumtest/xoo/sample-with-symlink/.gitignore
  15. 8
    10
      sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java
  16. 5
    2
      sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorStorage.java
  17. 5
    15
      sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/dependency/internal/DefaultDependency.java
  18. 56
    0
      sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/DefaultStorable.java
  19. 5
    13
      sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java
  20. 5
    16
      sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/measure/internal/DefaultMeasure.java
  21. 77
    0
      sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/test/TestCaseCoverage.java
  22. 10
    10
      sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/test/TestCaseExecution.java
  23. 138
    0
      sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/test/internal/DefaultTestCaseCoverage.java
  24. 19
    36
      sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/test/internal/DefaultTestCaseExecution.java
  25. 1
    1
      sonar-plugin-api/src/main/java/org/sonar/api/test/TestCase.java
  26. 10
    10
      sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/test/internal/DefaultTestCaseExecutionTest.java

+ 6
- 8
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/CoveragePerTestSensor.java View File

@@ -30,7 +30,6 @@ import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.sensor.Sensor;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.SensorDescriptor;
import org.sonar.api.batch.sensor.test.TestCase;
import org.sonar.xoo.Xoo;

import java.io.File;
@@ -81,13 +80,12 @@ public class CoveragePerTestSensor implements Sensor {
while (lines.hasNext()) {
coveredLines.add(Integer.parseInt(lines.next()));
}
TestCase testCase = context.newTestCase()
.inTestFile(testFile)
.name(testCaseName);
if (testCase == null) {
throw new IllegalStateException("No test case with name " + testCaseName + " on file " + testFile);
}
context.saveCoveragePerTest(testCase, mainFile, coveredLines);
context.newTestCaseCoverage()
.testFile(testFile)
.testName(testCaseName)
.cover(mainFile)
.onLines(coveredLines)
.save();
} catch (Exception e) {
throw new IllegalStateException("Error processing line " + lineNumber + " of file " + coverPerTest.getAbsolutePath(), e);
}

+ 4
- 4
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/TestCaseSensor.java View File

@@ -30,7 +30,7 @@ import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.sensor.Sensor;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.SensorDescriptor;
import org.sonar.api.batch.sensor.test.TestCase;
import org.sonar.api.batch.sensor.test.TestCaseExecution;
import org.sonar.xoo.Xoo;

import java.io.File;
@@ -77,11 +77,11 @@ public class TestCaseSensor implements Sensor {
String message = split.next();
String stack = split.next();
long duration = Long.parseLong(split.next());
context.newTestCase()
context.newTestCaseExecution()
.inTestFile(testFile)
.name(name)
.ofType(TestCase.Type.valueOf(type))
.status(TestCase.Status.valueOf(status))
.ofType(TestCaseExecution.Type.valueOf(type))
.status(TestCaseExecution.Status.valueOf(status))
.message(StringUtils.trimToNull(message))
.stackTrace(StringUtils.trimToNull(stack))
.durationInMs(duration)

+ 15
- 10
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/lang/CoveragePerTestSensorTest.java View File

@@ -32,8 +32,8 @@ import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.SensorStorage;
import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor;
import org.sonar.api.batch.sensor.test.TestCase;
import org.sonar.api.batch.sensor.test.internal.DefaultTestCase;
import org.sonar.api.batch.sensor.test.TestCaseCoverage;
import org.sonar.api.batch.sensor.test.internal.DefaultTestCaseCoverage;

import java.io.File;
import java.io.IOException;
@@ -86,19 +86,24 @@ public class CoveragePerTestSensorTest {

final SensorStorage sensorStorage = mock(SensorStorage.class);

when(context.newTestCase()).thenAnswer(new Answer<TestCase>() {
when(context.newTestCaseCoverage()).thenAnswer(new Answer<TestCaseCoverage>() {
@Override
public TestCase answer(InvocationOnMock invocation) throws Throwable {
return new DefaultTestCase(sensorStorage);
public TestCaseCoverage answer(InvocationOnMock invocation) throws Throwable {
return new DefaultTestCaseCoverage(sensorStorage);
}
});

TestCase test1 = new DefaultTestCase(null).inTestFile(testFile).name("test1");
TestCase test2 = new DefaultTestCase(null).inTestFile(testFile).name("test2");

sensor.execute(context);

verify(context).saveCoveragePerTest(test1, inputFile, Arrays.asList(1, 2, 3, 4));
verify(context).saveCoveragePerTest(test2, inputFile, Arrays.asList(5, 6, 7));
verify(sensorStorage).store(new DefaultTestCaseCoverage()
.testFile(testFile)
.testName("test1")
.cover(inputFile)
.onLines(Arrays.asList(1, 2, 3, 4)));
verify(sensorStorage).store(new DefaultTestCaseCoverage()
.testFile(testFile)
.testName("test2")
.cover(inputFile)
.onLines(Arrays.asList(5, 6, 7)));
}
}

+ 9
- 9
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/lang/TestCaseSensorTest.java View File

@@ -32,8 +32,8 @@ import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.SensorStorage;
import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor;
import org.sonar.api.batch.sensor.test.TestCase;
import org.sonar.api.batch.sensor.test.internal.DefaultTestCase;
import org.sonar.api.batch.sensor.test.TestCaseExecution;
import org.sonar.api.batch.sensor.test.internal.DefaultTestCaseExecution;

import java.io.File;
import java.io.IOException;
@@ -83,24 +83,24 @@ public class TestCaseSensorTest {

final SensorStorage sensorStorage = mock(SensorStorage.class);

when(context.newTestCase()).thenAnswer(new Answer<TestCase>() {
when(context.newTestCaseExecution()).thenAnswer(new Answer<TestCaseExecution>() {
@Override
public TestCase answer(InvocationOnMock invocation) throws Throwable {
return new DefaultTestCase(sensorStorage);
public TestCaseExecution answer(InvocationOnMock invocation) throws Throwable {
return new DefaultTestCaseExecution(sensorStorage);
}
});

sensor.execute(context);

verify(sensorStorage).store(new DefaultTestCase(null)
verify(sensorStorage).store(new DefaultTestCaseExecution(null)
.inTestFile(testFile)
.name("test1")
.durationInMs(10));
verify(sensorStorage).store(new DefaultTestCase(null)
verify(sensorStorage).store(new DefaultTestCaseExecution(null)
.inTestFile(testFile)
.name("test2")
.ofType(TestCase.Type.INTEGRATION)
.status(TestCase.Status.ERROR)
.ofType(TestCaseExecution.Type.INTEGRATION)
.status(TestCaseExecution.Status.ERROR)
.message("message")
.stackTrace("stack")
.durationInMs(15));

+ 13
- 12
sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java View File

@@ -36,7 +36,8 @@ import org.sonar.api.batch.sensor.issue.Issue;
import org.sonar.api.batch.sensor.measure.Measure;
import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
import org.sonar.api.batch.sensor.symbol.Symbol;
import org.sonar.api.batch.sensor.test.TestCase;
import org.sonar.api.batch.sensor.test.TestCaseCoverage;
import org.sonar.api.batch.sensor.test.TestCaseExecution;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Metric;
import org.sonar.api.platform.PluginMetadata;
@@ -61,8 +62,8 @@ import org.sonar.batch.scan2.MeasureCache;
import org.sonar.batch.scan2.ProjectScanContainer;
import org.sonar.batch.scan2.ScanTaskObserver;
import org.sonar.batch.symbol.SymbolData;
import org.sonar.batch.test.CoveragePerTestCache;
import org.sonar.batch.test.TestCaseCache;
import org.sonar.batch.test.TestCaseCoverageCache;
import org.sonar.batch.test.TestCaseExecutionCache;
import org.sonar.core.plugins.DefaultPluginMetadata;
import org.sonar.core.plugins.RemotePlugin;
import org.sonar.core.source.SnapshotDataTypes;
@@ -233,7 +234,7 @@ public class BatchMediumTester {
private Map<String, InputDir> inputDirs = new HashMap<String, InputDir>();
private Map<InputFile, SyntaxHighlightingData> highlightingPerFile = new HashMap<InputFile, SyntaxHighlightingData>();
private Map<InputFile, SymbolData> symbolTablePerFile = new HashMap<InputFile, SymbolData>();
private Map<String, Map<String, TestCase>> testCasesPerFile = new HashMap<String, Map<String, TestCase>>();
private Map<String, Map<String, TestCaseExecution>> testCasesPerFile = new HashMap<String, Map<String, TestCaseExecution>>();
private Map<String, Map<String, Map<String, List<Integer>>>> coveragePerTest = new HashMap<String, Map<String, Map<String, List<Integer>>>>();
private Map<String, Map<String, Integer>> dependencies = new HashMap<String, Map<String, Integer>>();

@@ -258,8 +259,8 @@ public class BatchMediumTester {
}

private void storeCoveragePerTest(ProjectScanContainer container) {
CoveragePerTestCache coveragePerTestCache = container.getComponentByType(CoveragePerTestCache.class);
for (Entry<List<Integer>> entry : coveragePerTestCache.entries()) {
TestCaseCoverageCache testCaseCoverageCache = container.getComponentByType(TestCaseCoverageCache.class);
for (Entry<TestCaseCoverage> entry : testCaseCoverageCache.entries()) {
String testFileKey = entry.key()[0].toString();
if (!coveragePerTest.containsKey(testFileKey)) {
coveragePerTest.put(testFileKey, new HashMap<String, Map<String, List<Integer>>>());
@@ -268,16 +269,16 @@ public class BatchMediumTester {
if (!coveragePerTest.get(testFileKey).containsKey(testName)) {
coveragePerTest.get(testFileKey).put(testName, new HashMap<String, List<Integer>>());
}
coveragePerTest.get(testFileKey).get(testName).put(entry.key()[2].toString(), entry.value());
coveragePerTest.get(testFileKey).get(testName).put(entry.key()[2].toString(), entry.value().coveredLines());
}
}

private void storeTestCases(ProjectScanContainer container) {
TestCaseCache testCaseCache = container.getComponentByType(TestCaseCache.class);
for (Entry<TestCase> entry : testCaseCache.entries()) {
TestCaseExecutionCache testCaseCache = container.getComponentByType(TestCaseExecutionCache.class);
for (Entry<TestCaseExecution> entry : testCaseCache.entries()) {
String effectiveKey = entry.key()[0].toString();
if (!testCasesPerFile.containsKey(effectiveKey)) {
testCasesPerFile.put(effectiveKey, new HashMap<String, TestCase>());
testCasesPerFile.put(effectiveKey, new HashMap<String, TestCaseExecution>());
}
testCasesPerFile.get(effectiveKey).put(entry.value().name(), entry.value());
}
@@ -358,7 +359,7 @@ public class BatchMediumTester {
return duplications.get(((DefaultInputFile) inputFile).key());
}

public Collection<TestCase> testCasesFor(InputFile inputFile) {
public Collection<TestCaseExecution> testCasesFor(InputFile inputFile) {
String key = ((DefaultInputFile) inputFile).key();
if (testCasesPerFile.containsKey(key)) {
return testCasesPerFile.get(key).values();
@@ -367,7 +368,7 @@ public class BatchMediumTester {
}
}

public TestCase testCase(InputFile inputFile, String testCaseName) {
public TestCaseExecution testCase(InputFile inputFile, String testCaseName) {
return testCasesPerFile.get(((DefaultInputFile) inputFile).key()).get(testCaseName);
}


+ 10
- 14
sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdapter.java View File

@@ -19,13 +19,11 @@
*/
package org.sonar.batch.scan;

import com.google.common.base.Preconditions;
import org.sonar.api.batch.Sensor;
import org.sonar.api.batch.SonarIndex;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputDir;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.InputFile.Type;
import org.sonar.api.batch.fs.InputPath;
import org.sonar.api.batch.rule.ActiveRules;
import org.sonar.api.batch.sensor.SensorContext;
@@ -33,8 +31,9 @@ import org.sonar.api.batch.sensor.issue.Issue;
import org.sonar.api.batch.sensor.issue.Issue.Severity;
import org.sonar.api.batch.sensor.measure.Measure;
import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
import org.sonar.api.batch.sensor.test.TestCase;
import org.sonar.api.batch.sensor.test.internal.DefaultTestCase;
import org.sonar.api.batch.sensor.test.TestCaseCoverage;
import org.sonar.api.batch.sensor.test.TestCaseExecution;
import org.sonar.api.batch.sensor.test.internal.DefaultTestCaseExecution;
import org.sonar.api.component.ResourcePerspectives;
import org.sonar.api.config.Settings;
import org.sonar.api.design.Dependency;
@@ -61,8 +60,6 @@ import org.sonar.batch.index.ComponentDataCache;
import org.sonar.batch.scan2.BaseSensorContext;
import org.sonar.core.component.ComponentKeys;

import java.util.List;

/**
* Implements {@link SensorContext} but forward everything to {@link org.sonar.api.batch.SensorContext} for backward compatibility.
* Will be dropped once old {@link Sensor} API is dropped.
@@ -178,8 +175,8 @@ public class SensorContextAdapter extends BaseSensorContext {
}

@Override
public void store(TestCase testCase) {
File testRes = getTestResource(((DefaultTestCase) testCase).testFile());
public void store(TestCaseExecution testCase) {
File testRes = getTestResource(((DefaultTestCaseExecution) testCase).testFile());
MutableTestPlan testPlan = perspectives.as(MutableTestPlan.class, testRes);
if (testPlan != null) {
testPlan
@@ -193,16 +190,15 @@ public class SensorContextAdapter extends BaseSensorContext {
}

@Override
public void saveCoveragePerTest(TestCase testCase, InputFile coveredFile, List<Integer> coveredLines) {
Preconditions.checkArgument(coveredFile.type() == Type.MAIN, "Should be a main file: " + coveredFile);
File testRes = getTestResource(((DefaultTestCase) testCase).testFile());
File mainRes = getMainResource(coveredFile);
public void store(TestCaseCoverage testCaseCoverage) {
File testRes = getTestResource(testCaseCoverage.testFile());
File mainRes = getMainResource(testCaseCoverage.coveredFile());
Testable testAbleFile = perspectives.as(MutableTestable.class, mainRes);
if (testAbleFile != null) {
MutableTestPlan testPlan = perspectives.as(MutableTestPlan.class, testRes);
if (testPlan != null) {
for (MutableTestCase mutableTestCase : testPlan.testCasesByName(testCase.name())) {
mutableTestCase.setCoverageBlock(testAbleFile, coveredLines);
for (MutableTestCase mutableTestCase : testPlan.testCasesByName(testCaseCoverage.testName())) {
mutableTestCase.setCoverageBlock(testAbleFile, testCaseCoverage.coveredLines());
}
} else {
throw new IllegalStateException("Unable to get MutableTestPlan perspective from " + testRes);

+ 11
- 4
sonar-batch/src/main/java/org/sonar/batch/scan2/BaseSensorContext.java View File

@@ -38,8 +38,10 @@ import org.sonar.api.batch.sensor.issue.internal.DefaultIssue;
import org.sonar.api.batch.sensor.measure.Measure;
import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
import org.sonar.api.batch.sensor.symbol.SymbolTableBuilder;
import org.sonar.api.batch.sensor.test.TestCase;
import org.sonar.api.batch.sensor.test.internal.DefaultTestCase;
import org.sonar.api.batch.sensor.test.TestCaseCoverage;
import org.sonar.api.batch.sensor.test.TestCaseExecution;
import org.sonar.api.batch.sensor.test.internal.DefaultTestCaseCoverage;
import org.sonar.api.batch.sensor.test.internal.DefaultTestCaseExecution;
import org.sonar.api.config.Settings;
import org.sonar.batch.duplication.BlockCache;
import org.sonar.batch.duplication.DefaultTokenBuilder;
@@ -153,8 +155,13 @@ public abstract class BaseSensorContext implements SensorContext, SensorStorage
}

@Override
public TestCase newTestCase() {
return new DefaultTestCase(this);
public TestCaseExecution newTestCaseExecution() {
return new DefaultTestCaseExecution(this);
}

@Override
public TestCaseCoverage newTestCaseCoverage() {
return new DefaultTestCaseCoverage(this);
}

@Override

+ 18
- 18
sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java View File

@@ -19,14 +19,12 @@
*/
package org.sonar.batch.scan2;

import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.bootstrap.ProjectDefinition;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.InputFile.Type;
import org.sonar.api.batch.fs.InputPath;
import org.sonar.api.batch.measure.Metric;
import org.sonar.api.batch.rule.ActiveRules;
@@ -36,8 +34,9 @@ import org.sonar.api.batch.sensor.issue.Issue;
import org.sonar.api.batch.sensor.issue.internal.DefaultIssue;
import org.sonar.api.batch.sensor.measure.Measure;
import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
import org.sonar.api.batch.sensor.test.TestCase;
import org.sonar.api.batch.sensor.test.internal.DefaultTestCase;
import org.sonar.api.batch.sensor.test.TestCaseCoverage;
import org.sonar.api.batch.sensor.test.TestCaseExecution;
import org.sonar.api.batch.sensor.test.internal.DefaultTestCaseExecution;
import org.sonar.api.config.Settings;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.rule.RuleKey;
@@ -48,8 +47,8 @@ import org.sonar.batch.duplication.DuplicationCache;
import org.sonar.batch.index.ComponentDataCache;
import org.sonar.batch.issue.IssueFilters;
import org.sonar.batch.scan.SensorContextAdapter;
import org.sonar.batch.test.CoveragePerTestCache;
import org.sonar.batch.test.TestCaseCache;
import org.sonar.batch.test.TestCaseCoverageCache;
import org.sonar.batch.test.TestCaseExecutionCache;
import org.sonar.core.component.ComponentKeys;

import java.io.Serializable;
@@ -89,20 +88,20 @@ public class DefaultSensorContext extends BaseSensorContext {
private final ProjectDefinition def;
private final ActiveRules activeRules;
private final IssueFilters issueFilters;
private final TestCaseCache testCaseCache;
private final CoveragePerTestCache coveragePerTestCache;
private final TestCaseExecutionCache testCaseExecutionCache;
private final TestCaseCoverageCache coveragePerTestCache;
private final DependencyCache dependencyCache;

public DefaultSensorContext(ProjectDefinition def, MeasureCache measureCache, IssueCache issueCache,
Settings settings, FileSystem fs, ActiveRules activeRules, IssueFilters issueFilters, ComponentDataCache componentDataCache,
BlockCache blockCache, DuplicationCache duplicationCache, TestCaseCache testCaseCache, CoveragePerTestCache coveragePerTestCache, DependencyCache dependencyCache) {
BlockCache blockCache, DuplicationCache duplicationCache, TestCaseExecutionCache testCaseCache, TestCaseCoverageCache coveragePerTestCache, DependencyCache dependencyCache) {
super(settings, fs, activeRules, componentDataCache, blockCache, duplicationCache);
this.def = def;
this.measureCache = measureCache;
this.issueCache = issueCache;
this.activeRules = activeRules;
this.issueFilters = issueFilters;
this.testCaseCache = testCaseCache;
this.testCaseExecutionCache = testCaseCache;
this.coveragePerTestCache = coveragePerTestCache;
this.dependencyCache = dependencyCache;
}
@@ -157,18 +156,19 @@ public class DefaultSensorContext extends BaseSensorContext {
}

@Override
public void store(TestCase testCase) {
if (testCaseCache.contains(((DefaultTestCase) testCase).testFile(), testCase.name())) {
throw new IllegalArgumentException("There is already a test case with the same name: " + testCase.name());
public void store(TestCaseExecution testCaseExecution) {
if (testCaseExecutionCache.contains(((DefaultTestCaseExecution) testCaseExecution).testFile(), testCaseExecution.name())) {
throw new IllegalArgumentException("There is already a test case with the same name: " + testCaseExecution.name());
}
testCaseCache.put(((DefaultTestCase) testCase).testFile(), testCase);
testCaseExecutionCache.put(((DefaultTestCaseExecution) testCaseExecution).testFile(), testCaseExecution);
}

@Override
public void saveCoveragePerTest(TestCase testCase, InputFile coveredFile, List<Integer> coveredLines) {
Preconditions.checkNotNull(testCase);
Preconditions.checkArgument(coveredFile.type() == Type.MAIN, "Should be a main file: " + coveredFile);
coveragePerTestCache.put(testCase, coveredFile, coveredLines);
public void store(TestCaseCoverage testCaseCoverage) {
if (coveragePerTestCache.getCoverage(testCaseCoverage.testFile(), testCaseCoverage.testName(), testCaseCoverage.coveredFile()) != null) {
throw new IllegalArgumentException("Test coverage already registered for this combination of test file, test name and main file: " + testCaseCoverage);
}
coveragePerTestCache.put(testCaseCoverage);
}

@Override

+ 4
- 4
sonar-batch/src/main/java/org/sonar/batch/scan2/ProjectScanContainer.java View File

@@ -48,8 +48,8 @@ import org.sonar.batch.scan.ProjectSettings;
import org.sonar.batch.scan.filesystem.InputPathCache;
import org.sonar.batch.scan.maven.FakeMavenPluginExecutor;
import org.sonar.batch.scan.maven.MavenPluginExecutor;
import org.sonar.batch.test.CoveragePerTestCache;
import org.sonar.batch.test.TestCaseCache;
import org.sonar.batch.test.TestCaseCoverageCache;
import org.sonar.batch.test.TestCaseExecutionCache;

public class ProjectScanContainer extends ComponentContainer {
public ProjectScanContainer(ComponentContainer taskContainer) {
@@ -118,8 +118,8 @@ public class ProjectScanContainer extends ComponentContainer {
DuplicationCache.class,

// Tests
TestCaseCache.class,
CoveragePerTestCache.class,
TestCaseExecutionCache.class,
TestCaseCoverageCache.class,

// Dependencies
DependencyCache.class,

+ 81
- 0
sonar-batch/src/main/java/org/sonar/batch/test/DefaultTestCaseCoverageValueCoder.java View File

@@ -0,0 +1,81 @@
/*
* 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.batch.test;

import com.persistit.Value;
import com.persistit.encoding.CoderContext;
import com.persistit.encoding.ValueCoder;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.sensor.test.internal.DefaultTestCaseCoverage;
import org.sonar.batch.scan.filesystem.InputPathCache;

import java.util.ArrayList;
import java.util.List;

class DefaultTestCaseCoverageValueCoder implements ValueCoder {

private InputPathCache inputPathCache;

public DefaultTestCaseCoverageValueCoder(InputPathCache inputPathCache) {
this.inputPathCache = inputPathCache;
}

@Override
public void put(Value value, Object object, CoderContext context) {
DefaultTestCaseCoverage t = (DefaultTestCaseCoverage) object;
value.putUTF(((DefaultInputFile) t.testFile()).moduleKey());
value.putUTF(((DefaultInputFile) t.testFile()).relativePath());
value.putUTF(t.testName());
value.putUTF(((DefaultInputFile) t.coveredFile()).moduleKey());
value.putUTF(((DefaultInputFile) t.coveredFile()).relativePath());
value.put(t.coveredLines().size());
for (Integer line : t.coveredLines()) {
value.put(line.intValue());
}
}

@Override
public Object get(Value value, Class clazz, CoderContext context) {
String testModuleKey = value.getString();
String testRelativePath = value.getString();
InputFile testFile = inputPathCache.getFile(testModuleKey, testRelativePath);
if (testFile == null) {
throw new IllegalStateException("Unable to load InputFile " + testModuleKey + ":" + testRelativePath);
}
String name = value.getString();
String mainModuleKey = value.getString();
String mainRelativePath = value.getString();
InputFile mainFile = inputPathCache.getFile(mainModuleKey, mainRelativePath);
if (mainFile == null) {
throw new IllegalStateException("Unable to load InputFile " + mainModuleKey + ":" + mainRelativePath);
}
int size = value.getInt();
List<Integer> lines = new ArrayList<Integer>(size);
for (int i = 0; i < size; i++) {
lines.add(value.getInt());
}
return new DefaultTestCaseCoverage()
.testFile(testFile)
.testName(name)
.cover(mainFile)
.onLines(lines);
}
}

sonar-batch/src/main/java/org/sonar/batch/test/DefaultTestCaseValueCoder.java → sonar-batch/src/main/java/org/sonar/batch/test/DefaultTestCaseExecutionValueCoder.java View File

@@ -24,23 +24,23 @@ import com.persistit.encoding.CoderContext;
import com.persistit.encoding.ValueCoder;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.sensor.test.TestCase;
import org.sonar.api.batch.sensor.test.internal.DefaultTestCase;
import org.sonar.api.batch.sensor.test.TestCaseExecution;
import org.sonar.api.batch.sensor.test.internal.DefaultTestCaseExecution;
import org.sonar.batch.scan.filesystem.InputPathCache;

import javax.annotation.Nullable;

class DefaultTestCaseValueCoder implements ValueCoder {
class DefaultTestCaseExecutionValueCoder implements ValueCoder {

private InputPathCache inputPathCache;

public DefaultTestCaseValueCoder(InputPathCache inputPathCache) {
public DefaultTestCaseExecutionValueCoder(InputPathCache inputPathCache) {
this.inputPathCache = inputPathCache;
}

@Override
public void put(Value value, Object object, CoderContext context) {
DefaultTestCase t = (DefaultTestCase) object;
DefaultTestCaseExecution t = (DefaultTestCaseExecution) object;
value.putUTF(((DefaultInputFile) t.testFile()).moduleKey());
value.putUTF(((DefaultInputFile) t.testFile()).relativePath());
value.putUTF(t.name());
@@ -72,9 +72,9 @@ class DefaultTestCaseValueCoder implements ValueCoder {
String message = value.getString();
String stack = value.getString();
long duration = value.getLong();
TestCase.Type type = TestCase.Type.values()[value.getInt()];
TestCase.Status status = TestCase.Status.values()[value.getInt()];
return new DefaultTestCase()
TestCaseExecution.Type type = TestCaseExecution.Type.values()[value.getInt()];
TestCaseExecution.Status status = TestCaseExecution.Status.values()[value.getInt()];
return new DefaultTestCaseExecution()
.inTestFile(testFile)
.ofType(type)
.name(name)

sonar-batch/src/main/java/org/sonar/batch/test/CoveragePerTestCache.java → sonar-batch/src/main/java/org/sonar/batch/test/TestCaseCoverageCache.java View File

@@ -23,43 +23,42 @@ import com.google.common.base.Preconditions;
import org.sonar.api.BatchComponent;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.sensor.test.TestCase;
import org.sonar.api.batch.sensor.test.internal.DefaultTestCase;
import org.sonar.api.batch.sensor.test.TestCaseCoverage;
import org.sonar.api.batch.sensor.test.internal.DefaultTestCaseCoverage;
import org.sonar.batch.index.Cache;
import org.sonar.batch.index.Cache.Entry;
import org.sonar.batch.index.Caches;
import org.sonar.batch.scan.filesystem.InputPathCache;

import javax.annotation.CheckForNull;

import java.util.List;

/**
* Cache of coverage per test. This cache is shared amongst all project modules.
*/
public class CoveragePerTestCache implements BatchComponent {
public class TestCaseCoverageCache implements BatchComponent {

private final Cache<List<Integer>> cache;
private final Cache<TestCaseCoverage> cache;

public CoveragePerTestCache(Caches caches) {
cache = caches.createCache("coveragePerTest");
public TestCaseCoverageCache(Caches caches, InputPathCache inputPathCache) {
caches.registerValueCoder(DefaultTestCaseCoverage.class, new DefaultTestCaseCoverageValueCoder(inputPathCache));
cache = caches.createCache("testCaseCoverage");
}

public Iterable<Entry<List<Integer>>> entries() {
public Iterable<Entry<TestCaseCoverage>> entries() {
return cache.entries();
}

@CheckForNull
public List<Integer> getCoveredLines(InputFile testFile, String testCaseName, InputFile mainFile) {
public TestCaseCoverage getCoverage(InputFile testFile, String testCaseName, InputFile mainFile) {
Preconditions.checkNotNull(testFile);
Preconditions.checkNotNull(testCaseName);
Preconditions.checkNotNull(mainFile);
return cache.get(((DefaultInputFile) testFile).key(), testCaseName, ((DefaultInputFile) mainFile).key());
}

public CoveragePerTestCache put(TestCase testCase, InputFile mainFile, List<Integer> coveredLines) {
Preconditions.checkNotNull(testCase);
Preconditions.checkNotNull(mainFile);
Preconditions.checkNotNull(coveredLines);
cache.put(((DefaultInputFile) ((DefaultTestCase) testCase).testFile()).key(), testCase.name(), ((DefaultInputFile) mainFile).key(), coveredLines);
public TestCaseCoverageCache put(TestCaseCoverage coverage) {
Preconditions.checkNotNull(coverage);
cache.put(((DefaultInputFile) coverage.testFile()).key(), coverage.testName(), ((DefaultInputFile) coverage.coveredFile()).key(), coverage);
return this;
}


sonar-batch/src/main/java/org/sonar/batch/test/TestCaseCache.java → sonar-batch/src/main/java/org/sonar/batch/test/TestCaseExecutionCache.java View File

@@ -23,8 +23,8 @@ import com.google.common.base.Preconditions;
import org.sonar.api.BatchComponent;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.sensor.test.TestCase;
import org.sonar.api.batch.sensor.test.internal.DefaultTestCase;
import org.sonar.api.batch.sensor.test.TestCaseExecution;
import org.sonar.api.batch.sensor.test.internal.DefaultTestCaseExecution;
import org.sonar.batch.index.Cache;
import org.sonar.batch.index.Cache.Entry;
import org.sonar.batch.index.Caches;
@@ -35,27 +35,27 @@ import javax.annotation.CheckForNull;
/**
* Cache of all TestCases. This cache is shared amongst all project modules.
*/
public class TestCaseCache implements BatchComponent {
public class TestCaseExecutionCache implements BatchComponent {

private final Cache<TestCase> cache;
private final Cache<TestCaseExecution> cache;

public TestCaseCache(Caches caches, InputPathCache inputPathCache) {
caches.registerValueCoder(DefaultTestCase.class, new DefaultTestCaseValueCoder(inputPathCache));
cache = caches.createCache("testCases");
public TestCaseExecutionCache(Caches caches, InputPathCache inputPathCache) {
caches.registerValueCoder(DefaultTestCaseExecution.class, new DefaultTestCaseExecutionValueCoder(inputPathCache));
cache = caches.createCache("testCaseExecutions");
}

public Iterable<Entry<TestCase>> entries() {
public Iterable<Entry<TestCaseExecution>> entries() {
return cache.entries();
}

@CheckForNull
public TestCase get(InputFile testFile, String testCaseName) {
public TestCaseExecution get(InputFile testFile, String testCaseName) {
Preconditions.checkNotNull(testFile);
Preconditions.checkNotNull(testCaseName);
return cache.get(((DefaultInputFile) testFile).key(), testCaseName);
}

public TestCaseCache put(InputFile testFile, TestCase testCase) {
public TestCaseExecutionCache put(InputFile testFile, TestCaseExecution testCase) {
Preconditions.checkNotNull(testFile);
Preconditions.checkNotNull(testCase);
cache.put(((DefaultInputFile) testFile).key(), testCase.name(), testCase);

+ 1
- 0
sonar-batch/src/test/resources/mediumtest/xoo/sample-with-symlink/.gitignore View File

@@ -0,0 +1 @@
.sonar

+ 8
- 10
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java View File

@@ -30,7 +30,8 @@ import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder;
import org.sonar.api.batch.sensor.issue.Issue;
import org.sonar.api.batch.sensor.measure.Measure;
import org.sonar.api.batch.sensor.symbol.SymbolTableBuilder;
import org.sonar.api.batch.sensor.test.TestCase;
import org.sonar.api.batch.sensor.test.TestCaseCoverage;
import org.sonar.api.batch.sensor.test.TestCaseExecution;
import org.sonar.api.config.Settings;

import java.io.Serializable;
@@ -112,21 +113,18 @@ public interface SensorContext {
// ------------ TESTS ------------

/**
* Create a new test case.
* Don't forget to call {@link TestCase#save()} once all parameters are provided.
* Create a new test case execution report.
* Don't forget to call {@link TestCaseExecution#save()} once all parameters are provided.
* @since 5.0
*/
TestCase newTestCase();
TestCaseExecution newTestCaseExecution();

/**
* Register coverage of a given test case on another main file. TestCase should have been registered using {@link #testPlanBuilder(InputFile)}
* @param testFile test file containing the test case
* @param testCaseName name of the test case
* @param coveredFile main file that is covered
* @param coveredLines list of covered lines
* Create a new test case coverage report.
* Don't forget to call {@link TestCaseCoverage#save()} once all parameters are provided.
* @since 5.0
*/
void saveCoveragePerTest(TestCase testCase, InputFile coveredFile, List<Integer> coveredLines);
TestCaseCoverage newTestCaseCoverage();

// ------------ DEPENDENCIES ------------


+ 5
- 2
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorStorage.java View File

@@ -22,7 +22,8 @@ package org.sonar.api.batch.sensor;
import org.sonar.api.batch.sensor.dependency.Dependency;
import org.sonar.api.batch.sensor.issue.Issue;
import org.sonar.api.batch.sensor.measure.Measure;
import org.sonar.api.batch.sensor.test.TestCase;
import org.sonar.api.batch.sensor.test.TestCaseCoverage;
import org.sonar.api.batch.sensor.test.TestCaseExecution;

/**
* Interface for storing data computed by sensors.
@@ -34,8 +35,10 @@ public interface SensorStorage {

void store(Issue issue);

void store(TestCase testCase);
void store(TestCaseExecution testCaseExecution);

void store(Dependency dependency);

void store(TestCaseCoverage testCaseCoverage);

}

+ 5
- 15
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/dependency/internal/DefaultDependency.java View File

@@ -22,28 +22,26 @@ package org.sonar.api.batch.sensor.dependency.internal;
import com.google.common.base.Preconditions;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.sensor.SensorStorage;
import org.sonar.api.batch.sensor.dependency.Dependency;
import org.sonar.api.batch.sensor.internal.DefaultStorable;

import javax.annotation.Nullable;

public class DefaultDependency implements Dependency {
public class DefaultDependency extends DefaultStorable implements Dependency {

private final SensorStorage storage;
private InputFile from;
private InputFile to;
private int weight = 1;
private boolean saved = false;

public DefaultDependency() {
this.storage = null;
super(null);
}

public DefaultDependency(@Nullable SensorStorage storage) {
this.storage = storage;
super(storage);
}

@Override
@@ -68,14 +66,11 @@ public class DefaultDependency implements Dependency {
}

@Override
public void save() {
Preconditions.checkNotNull(this.storage, "No persister on this object");
Preconditions.checkState(!saved, "This dependency was already saved");
public void doSave() {
Preconditions.checkState(!this.from.equals(this.to), "From and To can't be the same inputFile");
Preconditions.checkNotNull(this.from, "From inputFile can't be null");
Preconditions.checkNotNull(this.to, "To inputFile can't be null");
storage.store((Dependency) this);
this.saved = true;
}

@Override
@@ -123,9 +118,4 @@ public class DefaultDependency implements Dependency {
toHashCode();
}

@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}

}

+ 56
- 0
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/DefaultStorable.java View File

@@ -0,0 +1,56 @@
/*
* 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.base.Preconditions;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.sonar.api.batch.sensor.SensorStorage;

import javax.annotation.Nullable;

public abstract class DefaultStorable {

protected transient final SensorStorage storage;
private transient boolean saved = false;

public DefaultStorable() {
this.storage = null;
}

public DefaultStorable(@Nullable SensorStorage storage) {
this.storage = storage;
}

public final void save() {
Preconditions.checkNotNull(this.storage, "No persister on this object");
Preconditions.checkState(!saved, "This measure was already saved");
doSave();
this.saved = true;
}

protected abstract void doSave();

@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}

}

+ 5
- 13
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java View File

@@ -21,12 +21,11 @@ package org.sonar.api.batch.sensor.issue.internal;

import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
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.sensor.SensorStorage;
import org.sonar.api.batch.sensor.internal.DefaultStorable;
import org.sonar.api.batch.sensor.issue.Issue;
import org.sonar.api.rule.RuleKey;

@@ -35,7 +34,7 @@ import javax.annotation.Nullable;

import java.util.UUID;

public class DefaultIssue implements Issue {
public class DefaultIssue extends DefaultStorable implements Issue {

private static final String INPUT_DIR_SHOULD_BE_NON_NULL = "InputDir should be non null";
private static final String INPUT_FILE_SHOULD_BE_NON_NULL = "InputFile should be non null";
@@ -49,16 +48,15 @@ public class DefaultIssue implements Issue {
private Integer line;
private Double effortToFix;
private Severity overridenSeverity;
private final SensorStorage storage;

public DefaultIssue() {
super(null);
this.key = UUID.randomUUID().toString();
this.storage = null;
}

public DefaultIssue(SensorStorage storage) {
super(storage);
this.key = UUID.randomUUID().toString();
this.storage = storage;
}

@Override
@@ -154,11 +152,9 @@ public class DefaultIssue implements Issue {
}

@Override
public void save() {
Preconditions.checkNotNull(this.storage, "No persister on this object");
public void doSave() {
Preconditions.checkNotNull(this.ruleKey, "ruleKey is mandatory on issue");
Preconditions.checkState(!Strings.isNullOrEmpty(key), "Fail to generate issue key");

storage.store(this);
}

@@ -187,8 +183,4 @@ public class DefaultIssue implements Issue {
return key.hashCode();
}

@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}

+ 5
- 16
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/measure/internal/DefaultMeasure.java View File

@@ -22,11 +22,10 @@ package org.sonar.api.batch.sensor.measure.internal;
import com.google.common.base.Preconditions;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.measure.Metric;
import org.sonar.api.batch.sensor.SensorStorage;
import org.sonar.api.batch.sensor.internal.DefaultStorable;
import org.sonar.api.batch.sensor.measure.Measure;

import javax.annotation.CheckForNull;
@@ -34,22 +33,20 @@ import javax.annotation.Nullable;

import java.io.Serializable;

public class DefaultMeasure<G extends Serializable> implements Measure<G> {
public class DefaultMeasure<G extends Serializable> extends DefaultStorable implements Measure<G> {

private final SensorStorage storage;
private boolean onProject = false;
private InputFile file;
private Metric<G> metric;
private G value;
private boolean saved = false;
private boolean fromCore = false;

public DefaultMeasure() {
this.storage = null;
super();
}

public DefaultMeasure(@Nullable SensorStorage storage) {
this.storage = storage;
super(storage);
}

@Override
@@ -101,14 +98,11 @@ public class DefaultMeasure<G extends Serializable> implements Measure<G> {
}

@Override
public void save() {
Preconditions.checkNotNull(this.storage, "No persister on this object");
Preconditions.checkState(!saved, "This measure was already saved");
public void doSave() {
Preconditions.checkNotNull(this.value, "Measure value can't be null");
Preconditions.checkNotNull(this.metric, "Measure metric can't be null");
Preconditions.checkState(this.metric.valueType().equals(this.value.getClass()), "Measure value should be of type " + this.metric.valueType());
storage.store((Measure<Serializable>) this);
this.saved = true;
}

@Override
@@ -157,9 +151,4 @@ public class DefaultMeasure<G extends Serializable> implements Measure<G> {
toHashCode();
}

@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}

}

+ 77
- 0
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/test/TestCaseCoverage.java View File

@@ -0,0 +1,77 @@
/*
* 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.test;

import org.sonar.api.batch.fs.InputFile;

import java.util.List;

/**
* Represents line coverage produced by a single test in a test file on a single main file.
* @since 5.0
*/
public interface TestCaseCoverage {

/**
* InputFile where this test is located.
*/
InputFile testFile();

/**
* Set file where this test is located. Mandatory.
*/
TestCaseCoverage testFile(InputFile testFile);

/**
* Name of this test case.
*/
String testName();

/**
* Set name of this test. Name is mandatory.
*/
TestCaseCoverage testName(String name);

/**
* InputFile covered by this test.
*/
InputFile coveredFile();

/**
* Set file covered by this test. Mandatory.
*/
TestCaseCoverage cover(InputFile mainFile);

/**
* List of line numbers (1-based) covered by this test.
*/
List<Integer> coveredLines();

/**
* Set list of line numbers (1-based) covered by this test. Mandatory.
*/
TestCaseCoverage onLines(List<Integer> lines);

/**
* Call this method only once when your are done with defining the test case coverage.
*/
void save();

}

sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/test/TestCase.java → sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/test/TestCaseExecution.java View File

@@ -24,10 +24,10 @@ import org.sonar.api.batch.fs.InputFile;
import javax.annotation.Nullable;

/**
* Represents a single test in a test file.
* Represents result of execution of a single test in a test file.
* @since 5.0
*/
public interface TestCase {
public interface TestCaseExecution {

/**
* Test execution status.
@@ -55,7 +55,7 @@ public interface TestCase {
/**
* Set file where this test is located. Mandatory.
*/
TestCase inTestFile(InputFile testFile);
TestCaseExecution inTestFile(InputFile testFile);

/**
* Duration in milliseconds
@@ -65,7 +65,7 @@ public interface TestCase {
/**
* Duration in milliseconds
*/
TestCase durationInMs(long duration);
TestCaseExecution durationInMs(long duration);

/**
* Name of this test case.
@@ -75,7 +75,7 @@ public interface TestCase {
/**
* Set name of this test. Name is mandatory.
*/
TestCase name(String name);
TestCaseExecution name(String name);

/**
* Status of execution of the test.
@@ -85,7 +85,7 @@ public interface TestCase {
/**
* Status of execution of the test.
*/
TestCase status(Status status);
TestCaseExecution status(Status status);

/**
* Message (usually in case of {@link Status#ERROR} or {@link Status#FAILURE}).
@@ -95,7 +95,7 @@ public interface TestCase {
/**
* Message (usually in case of {@link Status#ERROR} or {@link Status#FAILURE}).
*/
TestCase message(String message);
TestCaseExecution message(String message);

/**
* Type of test.
@@ -105,7 +105,7 @@ public interface TestCase {
/**
* Type of test.
*/
TestCase ofType(Type type);
TestCaseExecution ofType(Type type);

/**
* Stacktrace (usually in case of {@link Status#ERROR}).
@@ -115,10 +115,10 @@ public interface TestCase {
/**
* Set stacktrace (usually in case of {@link Status#ERROR}).
*/
TestCase stackTrace(String stackTrace);
TestCaseExecution stackTrace(String stackTrace);

/**
* Call this method only once when your are done with defining the test case.
* Call this method only once when your are done with defining the test case execution.
*/
void save();


+ 138
- 0
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/test/internal/DefaultTestCaseCoverage.java View File

@@ -0,0 +1,138 @@
/*
* 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.test.internal;

import com.google.common.base.Preconditions;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.sensor.SensorStorage;
import org.sonar.api.batch.sensor.internal.DefaultStorable;
import org.sonar.api.batch.sensor.test.TestCaseCoverage;

import java.util.Collections;
import java.util.List;

public class DefaultTestCaseCoverage extends DefaultStorable implements TestCaseCoverage {

private InputFile testFile;
private InputFile mainFile;
private String name;
private List<Integer> lines;

public DefaultTestCaseCoverage() {
super(null);
}

public DefaultTestCaseCoverage(SensorStorage storage) {
super(storage);
}

@Override
public InputFile testFile() {
return testFile;
}

@Override
public DefaultTestCaseCoverage testFile(InputFile testFile) {
Preconditions.checkNotNull(testFile, "TestFile cannot be null");
Preconditions.checkArgument(testFile.type() == InputFile.Type.TEST, "Should be a test file: " + testFile);
this.testFile = testFile;
return this;
}

@Override
public InputFile coveredFile() {
return mainFile;
}

@Override
public TestCaseCoverage cover(InputFile mainFile) {
Preconditions.checkNotNull(mainFile, "InputFile cannot be null");
Preconditions.checkArgument(mainFile.type() == InputFile.Type.MAIN, "Should be a main file: " + mainFile);
this.mainFile = mainFile;
return this;
}

@Override
public DefaultTestCaseCoverage testName(String name) {
Preconditions.checkArgument(StringUtils.isNotBlank(name), "Test name is mandatory and should not be blank");
this.name = name;
return this;
}

@Override
public String testName() {
return name;
}

@Override
public List<Integer> coveredLines() {
return Collections.unmodifiableList(lines);
}

@Override
public TestCaseCoverage onLines(List<Integer> lines) {
Preconditions.checkNotNull(lines, "Lines list cannot be null");
Preconditions.checkArgument(lines.size() > 0, "No need to register test coverage if no line is covered");
this.lines = lines;
return this;
}

@Override
public void doSave() {
Preconditions.checkNotNull(testFile, "TestFile is mandatory");
Preconditions.checkNotNull(mainFile, "MainFile is mandatory");
Preconditions.checkNotNull(name, "Test name is mandatory");
Preconditions.checkNotNull(lines, "Lines are mandatory");
storage.store(this);
}

// Just for unit tests
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (obj.getClass() != getClass()) {
return false;
}
DefaultTestCaseCoverage rhs = (DefaultTestCaseCoverage) obj;
return new EqualsBuilder()
.append(testFile, rhs.testFile)
.append(name, rhs.name)
.append(mainFile, rhs.mainFile)
.append(lines.toArray(), rhs.lines.toArray())
.isEquals();
}

@Override
public int hashCode() {
return new HashCodeBuilder(13, 43)
.append(testFile)
.append(name)
.append(mainFile)
.toHashCode();
}
}

sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/test/internal/DefaultTestCase.java → sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/test/internal/DefaultTestCaseExecution.java View File

@@ -23,36 +23,34 @@ import com.google.common.base.Preconditions;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.sensor.SensorStorage;
import org.sonar.api.batch.sensor.test.TestCase;
import org.sonar.api.batch.sensor.internal.DefaultStorable;
import org.sonar.api.batch.sensor.test.TestCaseExecution;

import javax.annotation.CheckForNull;
import javax.annotation.Nullable;

public class DefaultTestCase implements TestCase {
public class DefaultTestCaseExecution extends DefaultStorable implements TestCaseExecution {

private final SensorStorage storage;
private InputFile testFile;
private String name;
private Long duration;
private TestCase.Status status = Status.OK;
private TestCaseExecution.Status status = Status.OK;
private String message;
private TestCase.Type type = Type.UNIT;
private TestCaseExecution.Type type = Type.UNIT;
private String stackTrace;

public DefaultTestCase() {
this.storage = null;
public DefaultTestCaseExecution() {
super(null);
}

public DefaultTestCase(SensorStorage storage) {
this.storage = storage;
public DefaultTestCaseExecution(SensorStorage storage) {
super(storage);
}

@Override
public DefaultTestCase inTestFile(InputFile testFile) {
public DefaultTestCaseExecution inTestFile(InputFile testFile) {
Preconditions.checkNotNull(testFile, "TestFile cannot be null");
Preconditions.checkArgument(testFile.type() == InputFile.Type.TEST, "Should be a test file: " + testFile);
this.testFile = testFile;
@@ -60,41 +58,41 @@ public class DefaultTestCase implements TestCase {
}

@Override
public DefaultTestCase name(String name) {
public DefaultTestCaseExecution name(String name) {
Preconditions.checkArgument(StringUtils.isNotBlank(name), "Test name is mandatory and should not be blank");
this.name = name;
return this;
}

@Override
public DefaultTestCase durationInMs(long duration) {
public DefaultTestCaseExecution durationInMs(long duration) {
Preconditions.checkArgument(duration >= 0, "Test duration must be positive (got: " + duration + ")");
this.duration = duration;
return this;
}

@Override
public DefaultTestCase status(TestCase.Status status) {
public DefaultTestCaseExecution status(TestCaseExecution.Status status) {
Preconditions.checkNotNull(status);
this.status = status;
return this;
}

@Override
public DefaultTestCase message(@Nullable String message) {
public DefaultTestCaseExecution message(@Nullable String message) {
this.message = message;
return this;
}

@Override
public DefaultTestCase ofType(TestCase.Type type) {
public DefaultTestCaseExecution ofType(TestCaseExecution.Type type) {
Preconditions.checkNotNull(type);
this.type = type;
return this;
}

@Override
public DefaultTestCase stackTrace(@Nullable String stackTrace) {
public DefaultTestCaseExecution stackTrace(@Nullable String stackTrace) {
this.stackTrace = stackTrace;
return this;
}
@@ -138,10 +136,9 @@ public class DefaultTestCase implements TestCase {
}

@Override
public void save() {
Preconditions.checkNotNull(this.storage, "No persister on this object");
public void doSave() {
Preconditions.checkNotNull(testFile, "TestFile is mandatory");
Preconditions.checkNotNull(name, "TestFile is mandatory");
Preconditions.checkNotNull(name, "Test name is mandatory");
storage.store(this);
}

@@ -157,7 +154,7 @@ public class DefaultTestCase implements TestCase {
if (obj.getClass() != getClass()) {
return false;
}
DefaultTestCase rhs = (DefaultTestCase) obj;
DefaultTestCaseExecution rhs = (DefaultTestCaseExecution) obj;
return new EqualsBuilder()
.append(testFile, rhs.testFile)
.append(name, rhs.name)
@@ -181,18 +178,4 @@ public class DefaultTestCase implements TestCase {
.append(stackTrace)
.toHashCode();
}

@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
.append("file", testFile)
.append("name", name)
.append("duration", duration)
.append("status", status)
.append("message", message)
.append("type", type)
.append("stackTrace", stackTrace)
.toString();
}

}

+ 1
- 1
sonar-plugin-api/src/main/java/org/sonar/api/test/TestCase.java View File

@@ -22,7 +22,7 @@ package org.sonar.api.test;
import javax.annotation.Nullable;

/**
* @deprecated since 5.0 see {@link org.sonar.api.batch.sensor.test.TestCase}
* @deprecated since 5.0 see {@link org.sonar.api.batch.sensor.test.TestCaseExecution}
*/
@Deprecated
public interface TestCase {

sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/test/internal/DefaultTestCaseTest.java → sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/test/internal/DefaultTestCaseExecutionTest.java View File

@@ -24,12 +24,12 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.sensor.test.TestCase.Status;
import org.sonar.api.batch.sensor.test.TestCase.Type;
import org.sonar.api.batch.sensor.test.TestCaseExecution.Status;
import org.sonar.api.batch.sensor.test.TestCaseExecution.Type;

import static org.fest.assertions.Assertions.assertThat;

public class DefaultTestCaseTest {
public class DefaultTestCaseExecutionTest {

@Rule
public ExpectedException thrown = ExpectedException.none();
@@ -38,7 +38,7 @@ public class DefaultTestCaseTest {

@Test
public void testCreation() throws Exception {
DefaultTestCase testCase = new DefaultTestCase(null)
DefaultTestCaseExecution testCase = new DefaultTestCaseExecution(null)
.inTestFile(parent)
.name("myTest")
.durationInMs(1)
@@ -58,7 +58,7 @@ public class DefaultTestCaseTest {

@Test
public void testCreationWithDefaultValues() throws Exception {
DefaultTestCase testCase = new DefaultTestCase(null)
DefaultTestCaseExecution testCase = new DefaultTestCaseExecution(null)
.inTestFile(parent)
.name("myTest");

@@ -73,7 +73,7 @@ public class DefaultTestCaseTest {

@Test
public void testInvalidDuration() throws Exception {
DefaultTestCase builder = new DefaultTestCase(null)
DefaultTestCaseExecution builder = new DefaultTestCaseExecution(null)
.inTestFile(parent)
.name("myTest");

@@ -84,7 +84,7 @@ public class DefaultTestCaseTest {

@Test
public void testEqualsHashCodeToString() {
DefaultTestCase testCase1 = new DefaultTestCase(null)
DefaultTestCaseExecution testCase1 = new DefaultTestCaseExecution(null)
.inTestFile(parent)
.name("myTest")
.durationInMs(1)
@@ -92,7 +92,7 @@ public class DefaultTestCaseTest {
.stackTrace("stack")
.status(Status.ERROR)
.ofType(Type.UNIT);
DefaultTestCase testCase1a = new DefaultTestCase(null)
DefaultTestCaseExecution testCase1a = new DefaultTestCaseExecution(null)
.inTestFile(parent)
.name("myTest")
.durationInMs(1)
@@ -100,7 +100,7 @@ public class DefaultTestCaseTest {
.stackTrace("stack")
.status(Status.ERROR)
.ofType(Type.UNIT);
DefaultTestCase testCase2 = new DefaultTestCase(null)
DefaultTestCaseExecution testCase2 = new DefaultTestCaseExecution(null)
.inTestFile(new DefaultInputFile("foo2", "src/Foo.php").setType(InputFile.Type.TEST))
.name("myTest2")
.durationInMs(2)
@@ -116,7 +116,7 @@ public class DefaultTestCaseTest {
assertThat(testCase1).isNotEqualTo("foo");

assertThat(testCase1.toString()).isEqualTo(
"DefaultTestCase[file=[moduleKey=foo, relative=src/Foo.php, abs=null],name=myTest,duration=1,status=ERROR,message=message,type=UNIT,stackTrace=stack]");
"DefaultTestCaseExecution[testFile=[moduleKey=foo, relative=src/Foo.php, abs=null],name=myTest,duration=1,status=ERROR,message=message,type=UNIT,stackTrace=stack]");
assertThat(testCase1.hashCode()).isEqualTo(testCase1a.hashCode());
}


Loading…
Cancel
Save