From d4beaec49895945d23d509d1502790794b008d56 Mon Sep 17 00:00:00 2001 From: simonbrandhof Date: Fri, 25 Feb 2011 00:16:09 +0100 Subject: [PATCH] SONAR-2218 Add CoverageMeasuresBuilder to API + add Metric.Builder, first step to get an immutable Metric class --- .../api/AbstractCoberturaParser.java | 122 ++++--------- .../cobertura/CoberturaSensorTest.java | 53 ++---- .../sonar/batch/index/MeasurePersister.java | 5 +- .../org/sonar/api/measures/CoreMetrics.java | 107 ++++++++++-- .../api/measures/CoverageMeasuresBuilder.java | 161 ++++++++++++++++++ .../java/org/sonar/api/measures/Measure.java | 72 ++++---- .../java/org/sonar/api/measures/Metric.java | 97 ++++++++++- .../measures/CoverageMeasuresBuilderTest.java | 128 ++++++++++++++ .../org/sonar/api/measures/MetricTest.java | 59 +++++++ 9 files changed, 622 insertions(+), 182 deletions(-) create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/measures/CoverageMeasuresBuilder.java create mode 100644 sonar-plugin-api/src/test/java/org/sonar/api/measures/CoverageMeasuresBuilderTest.java create mode 100644 sonar-plugin-api/src/test/java/org/sonar/api/measures/MetricTest.java diff --git a/plugins/sonar-cobertura-plugin/src/main/java/org/sonar/plugins/cobertura/api/AbstractCoberturaParser.java b/plugins/sonar-cobertura-plugin/src/main/java/org/sonar/plugins/cobertura/api/AbstractCoberturaParser.java index bd095688fb0..3507795e693 100644 --- a/plugins/sonar-cobertura-plugin/src/main/java/org/sonar/plugins/cobertura/api/AbstractCoberturaParser.java +++ b/plugins/sonar-cobertura-plugin/src/main/java/org/sonar/plugins/cobertura/api/AbstractCoberturaParser.java @@ -19,36 +19,31 @@ */ package org.sonar.plugins.cobertura.api; -import static java.util.Locale.ENGLISH; -import static org.sonar.api.utils.ParsingUtils.parseNumber; -import static org.sonar.api.utils.ParsingUtils.scaleValue; - +import com.google.common.collect.Maps; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang.StringUtils; import org.codehaus.staxmate.in.SMHierarchicCursor; import org.codehaus.staxmate.in.SMInputCursor; import org.sonar.api.batch.SensorContext; -import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.CoverageMeasuresBuilder; import org.sonar.api.measures.Measure; -import org.sonar.api.measures.PersistenceMode; -import org.sonar.api.measures.PropertiesBuilder; import org.sonar.api.resources.Resource; import org.sonar.api.utils.StaxParser; import org.sonar.api.utils.XmlParserException; +import javax.xml.stream.XMLStreamException; import java.io.File; import java.text.ParseException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; import java.util.Map; -import javax.xml.stream.XMLStreamException; +import static java.util.Locale.ENGLISH; +import static org.sonar.api.utils.ParsingUtils.parseNumber; /** * @since 2.4 */ public abstract class AbstractCoberturaParser { + public void parseReport(File xmlFile, final SensorContext context) { try { StaxParser parser = new StaxParser(new StaxParser.XmlStreamHandler() { @@ -70,110 +65,57 @@ public abstract class AbstractCoberturaParser { private void collectPackageMeasures(SMInputCursor pack, SensorContext context) throws ParseException, XMLStreamException { while (pack.getNext() != null) { - Map fileDataPerFilename = new HashMap(); - collectFileMeasures(pack.descendantElementCursor("class"), fileDataPerFilename); - for (FileData cci : fileDataPerFilename.values()) { - if (isFileExist(context, cci.getFile())) { - for (Measure measure : cci.getMeasures()) { - context.saveMeasure(cci.getFile(), measure); + Map builderByFilename = Maps.newHashMap(); + collectFileMeasures(pack.descendantElementCursor("class"), builderByFilename); + for (Map.Entry entry : builderByFilename.entrySet()) { + String filename = sanitizeFilename(entry.getKey()); + Resource file = getResource(filename); + if (fileExists(context, file)) { + for (Measure measure : entry.getValue().createMeasures()) { + System.out.println(file + " -> " + measure); + context.saveMeasure(file, measure); } } } } } - private boolean isFileExist(SensorContext context, Resource file) { + private boolean fileExists(SensorContext context, Resource file) { return context.getResource(file) != null; } - private void collectFileMeasures(SMInputCursor clazz, Map dataPerFilename) throws ParseException, XMLStreamException { + private void collectFileMeasures(SMInputCursor clazz, Map builderByFilename) throws ParseException, XMLStreamException { while (clazz.getNext() != null) { - String fileName = FilenameUtils.removeExtension(clazz.getAttrValue("filename")); - fileName = fileName.replace('/', '.').replace('\\', '.'); - FileData data = dataPerFilename.get(fileName); - if (data == null) { - data = new FileData(getResource(fileName)); - dataPerFilename.put(fileName, data); + String fileName = clazz.getAttrValue("filename"); + CoverageMeasuresBuilder builder = builderByFilename.get(fileName); + if (builder==null) { + builder = CoverageMeasuresBuilder.create(); + builderByFilename.put(fileName, builder); } - collectFileData(clazz, data); + collectFileData(clazz, builder); } } - private void collectFileData(SMInputCursor clazz, FileData data) throws ParseException, XMLStreamException { + private void collectFileData(SMInputCursor clazz, CoverageMeasuresBuilder builder) throws ParseException, XMLStreamException { SMInputCursor line = clazz.childElementCursor("lines").advance().childElementCursor("line"); while (line.getNext() != null) { - String lineId = line.getAttrValue("number"); - data.addLine(lineId, (int) parseNumber(line.getAttrValue("hits"), ENGLISH)); + int lineId = Integer.parseInt(line.getAttrValue("number")); + builder.setHits(lineId, (int) parseNumber(line.getAttrValue("hits"), ENGLISH)); String isBranch = line.getAttrValue("branch"); String text = line.getAttrValue("condition-coverage"); if (StringUtils.equals(isBranch, "true") && StringUtils.isNotBlank(text)) { String[] conditions = StringUtils.split(StringUtils.substringBetween(text, "(", ")"), "/"); - data.addConditionLine(lineId, Integer.parseInt(conditions[0]), Integer.parseInt(conditions[1]), StringUtils.substringBefore(text, " ")); - } - } - } - - private class FileData { - - private int lines = 0; - private int conditions = 0; - private int coveredLines = 0; - private int coveredConditions = 0; - - private Resource file; - private PropertiesBuilder lineHitsBuilder = new PropertiesBuilder(CoreMetrics.COVERAGE_LINE_HITS_DATA); - private PropertiesBuilder branchHitsBuilder = new PropertiesBuilder(CoreMetrics.BRANCH_COVERAGE_HITS_DATA); - - public void addLine(String lineId, int lineHits) { - lines++; - if (lineHits > 0) { - coveredLines++; - } - lineHitsBuilder.add(lineId, lineHits); - } - - public void addConditionLine(String lineId, int coveredConditions, int conditions, String label) { - this.conditions += conditions; - this.coveredConditions += coveredConditions; - branchHitsBuilder.add(lineId, label); - } - - public FileData(Resource file) { - this.file = file; - } - - public List getMeasures() { - List measures = new ArrayList(); - if (lines > 0) { - measures.add(new Measure(CoreMetrics.COVERAGE, calculateCoverage(coveredLines + coveredConditions, lines + conditions))); - - measures.add(new Measure(CoreMetrics.LINE_COVERAGE, calculateCoverage(coveredLines, lines))); - measures.add(new Measure(CoreMetrics.LINES_TO_COVER, (double) lines)); - measures.add(new Measure(CoreMetrics.UNCOVERED_LINES, (double) lines - coveredLines)); - measures.add(lineHitsBuilder.build().setPersistenceMode(PersistenceMode.DATABASE)); - - if (conditions > 0) { - measures.add(new Measure(CoreMetrics.BRANCH_COVERAGE, calculateCoverage(coveredConditions, conditions))); - measures.add(new Measure(CoreMetrics.CONDITIONS_TO_COVER, (double) conditions)); - measures.add(new Measure(CoreMetrics.UNCOVERED_CONDITIONS, (double) conditions - coveredConditions)); - measures.add(branchHitsBuilder.build().setPersistenceMode(PersistenceMode.DATABASE)); - } + builder.setConditions(lineId, Integer.parseInt(conditions[1]), Integer.parseInt(conditions[0])); } - return measures; - } - - public Resource getFile() { - return file; } } - private double calculateCoverage(int coveredElements, int elements) { - if (elements > 0) { - return scaleValue(100.0 * ((double) coveredElements / (double) elements)); - } - return 0.0; + private String sanitizeFilename(String s) { + String fileName = FilenameUtils.removeExtension(s); + fileName = fileName.replace('/', '.').replace('\\', '.'); + return fileName; } - protected abstract Resource getResource(String fileName); + protected abstract Resource getResource(String fileName); } diff --git a/plugins/sonar-cobertura-plugin/src/test/java/org/sonar/plugins/cobertura/CoberturaSensorTest.java b/plugins/sonar-cobertura-plugin/src/test/java/org/sonar/plugins/cobertura/CoberturaSensorTest.java index 0b57c57523c..8b5c57f7e3f 100644 --- a/plugins/sonar-cobertura-plugin/src/test/java/org/sonar/plugins/cobertura/CoberturaSensorTest.java +++ b/plugins/sonar-cobertura-plugin/src/test/java/org/sonar/plugins/cobertura/CoberturaSensorTest.java @@ -23,9 +23,7 @@ import org.junit.Test; import org.sonar.api.batch.SensorContext; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.Measure; -import org.sonar.api.resources.JavaFile; -import org.sonar.api.resources.JavaPackage; -import org.sonar.api.resources.Resource; +import org.sonar.api.resources.*; import org.sonar.api.test.IsMeasure; import org.sonar.api.test.IsResource; @@ -37,10 +35,7 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyDouble; import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; public class CoberturaSensorTest { @@ -103,7 +98,6 @@ public class CoberturaSensorTest { new CoberturaSensor().parseReport(getCoverageReport(), context); final JavaFile file = new JavaFile("org.apache.commons.chain.config.ConfigParser"); - // verify(context).saveMeasure(eq(file), argThat(new IsMeasure(CoreMetrics.LINE_COVERAGE, 83.3))); verify(context).saveMeasure(eq(file), argThat(new IsMeasure(CoreMetrics.LINES_TO_COVER, 30.0))); verify(context).saveMeasure(eq(file), argThat(new IsMeasure(CoreMetrics.UNCOVERED_LINES, 5.0))); } @@ -115,7 +109,6 @@ public class CoberturaSensorTest { new CoberturaSensor().parseReport(getCoverageReport(), context); final JavaFile file = new JavaFile("org.apache.commons.chain.config.ConfigParser"); - verify(context).saveMeasure(eq(file), argThat(new IsMeasure(CoreMetrics.BRANCH_COVERAGE, 66.7))); verify(context).saveMeasure(eq(file), argThat(new IsMeasure(CoreMetrics.CONDITIONS_TO_COVER, 6.0))); verify(context).saveMeasure(eq(file), argThat(new IsMeasure(CoreMetrics.UNCOVERED_CONDITIONS, 2.0))); } @@ -153,56 +146,40 @@ public class CoberturaSensorTest { when(context.getResource(any(Resource.class))).thenReturn(new JavaFile("org.sonar.MyClass")); new CoberturaSensor().parseReport(coverage, context); - verify(context).saveMeasure(argThat(new IsResource(Resource.SCOPE_ENTITY, Resource.QUALIFIER_CLASS, "org.sonar.samples.InnerClass")), - argThat(new IsMeasure(CoreMetrics.LINE_COVERAGE, 37.1))); - verify(context).saveMeasure(argThat(new IsResource(Resource.SCOPE_ENTITY, Resource.QUALIFIER_CLASS, "org.sonar.samples.InnerClass")), + verify(context).saveMeasure(argThat(new IsResource(Scopes.FILE, Qualifiers.CLASS, "org.sonar.samples.InnerClass")), argThat(new IsMeasure(CoreMetrics.LINES_TO_COVER, 35.0))); - verify(context).saveMeasure(argThat(new IsResource(Resource.SCOPE_ENTITY, Resource.QUALIFIER_CLASS, "org.sonar.samples.InnerClass")), + verify(context).saveMeasure(argThat(new IsResource(Scopes.FILE, Qualifiers.CLASS, "org.sonar.samples.InnerClass")), argThat(new IsMeasure(CoreMetrics.UNCOVERED_LINES, 22.0))); - verify(context).saveMeasure(argThat(new IsResource(Resource.SCOPE_ENTITY, Resource.QUALIFIER_CLASS, "org.sonar.samples.InnerClass")), - argThat(new IsMeasure(CoreMetrics.BRANCH_COVERAGE, 25.0))); - verify(context).saveMeasure(argThat(new IsResource(Resource.SCOPE_ENTITY, Resource.QUALIFIER_CLASS, "org.sonar.samples.InnerClass")), + verify(context).saveMeasure(argThat(new IsResource(Scopes.FILE, Qualifiers.CLASS, "org.sonar.samples.InnerClass")), argThat(new IsMeasure(CoreMetrics.CONDITIONS_TO_COVER, 4.0))); - verify(context).saveMeasure(argThat(new IsResource(Resource.SCOPE_ENTITY, Resource.QUALIFIER_CLASS, "org.sonar.samples.InnerClass")), + verify(context).saveMeasure(argThat(new IsResource(Scopes.FILE, Qualifiers.CLASS, "org.sonar.samples.InnerClass")), argThat(new IsMeasure(CoreMetrics.UNCOVERED_CONDITIONS, 3.0))); verify(context, never()).saveMeasure( - argThat(new IsResource(Resource.SCOPE_ENTITY, Resource.QUALIFIER_CLASS, "org.sonar.samples.InnerClass$InnerClassInside")), - argThat(new IsMeasure(CoreMetrics.LINE_COVERAGE))); - verify(context, never()).saveMeasure( - argThat(new IsResource(Resource.SCOPE_ENTITY, Resource.QUALIFIER_CLASS, "org.sonar.samples.InnerClass$InnerClassInside")), - argThat(new IsMeasure(CoreMetrics.BRANCH_COVERAGE))); - verify(context, never()).saveMeasure( - argThat(new IsResource(Resource.SCOPE_ENTITY, Resource.QUALIFIER_CLASS, "org.sonar.samples.InnerClass$InnerClassInside")), + argThat(new IsResource(Scopes.FILE, Qualifiers.CLASS, "org.sonar.samples.InnerClass$InnerClassInside")), argThat(new IsMeasure(CoreMetrics.LINES_TO_COVER))); verify(context, never()).saveMeasure( - argThat(new IsResource(Resource.SCOPE_ENTITY, Resource.QUALIFIER_CLASS, "org.sonar.samples.InnerClass$InnerClassInside")), + argThat(new IsResource(Scopes.FILE, Qualifiers.CLASS, "org.sonar.samples.InnerClass$InnerClassInside")), argThat(new IsMeasure(CoreMetrics.CONDITIONS_TO_COVER))); verify(context, never()).saveMeasure( - argThat(new IsResource(Resource.SCOPE_ENTITY, Resource.QUALIFIER_CLASS, "org.sonar.samples.InnerClass$InnerClassInside")), + argThat(new IsResource(Scopes.FILE, Qualifiers.CLASS, "org.sonar.samples.InnerClass$InnerClassInside")), argThat(new IsMeasure(CoreMetrics.UNCOVERED_CONDITIONS))); verify(context, never()).saveMeasure( - argThat(new IsResource(Resource.SCOPE_ENTITY, Resource.QUALIFIER_CLASS, "org.sonar.samples.InnerClass$InnerClassInside")), + argThat(new IsResource(Scopes.FILE, Qualifiers.CLASS, "org.sonar.samples.InnerClass$InnerClassInside")), argThat(new IsMeasure(CoreMetrics.UNCOVERED_LINES))); verify(context, never()).saveMeasure( - argThat(new IsResource(Resource.SCOPE_ENTITY, Resource.QUALIFIER_CLASS, "org.sonar.samples.PrivateClass")), - argThat(new IsMeasure(CoreMetrics.LINE_COVERAGE))); - verify(context, never()).saveMeasure( - argThat(new IsResource(Resource.SCOPE_ENTITY, Resource.QUALIFIER_CLASS, "org.sonar.samples.PrivateClass")), - argThat(new IsMeasure(CoreMetrics.BRANCH_COVERAGE))); - verify(context, never()).saveMeasure( - argThat(new IsResource(Resource.SCOPE_ENTITY, Resource.QUALIFIER_CLASS, "org.sonar.samples.PrivateClass")), + argThat(new IsResource(Scopes.FILE, Qualifiers.CLASS, "org.sonar.samples.PrivateClass")), argThat(new IsMeasure(CoreMetrics.LINES_TO_COVER))); verify(context, never()).saveMeasure( - argThat(new IsResource(Resource.SCOPE_ENTITY, Resource.QUALIFIER_CLASS, "org.sonar.samples.PrivateClass")), + argThat(new IsResource(Scopes.FILE, Qualifiers.CLASS, "org.sonar.samples.PrivateClass")), argThat(new IsMeasure(CoreMetrics.CONDITIONS_TO_COVER))); verify(context, never()).saveMeasure( - argThat(new IsResource(Resource.SCOPE_ENTITY, Resource.QUALIFIER_CLASS, "org.sonar.samples.PrivateClass")), + argThat(new IsResource(Scopes.FILE, Qualifiers.CLASS, "org.sonar.samples.PrivateClass")), argThat(new IsMeasure(CoreMetrics.UNCOVERED_CONDITIONS))); verify(context, never()).saveMeasure( - argThat(new IsResource(Resource.SCOPE_ENTITY, Resource.QUALIFIER_CLASS, "org.sonar.samples.PrivateClass")), + argThat(new IsResource(Scopes.FILE, Qualifiers.CLASS, "org.sonar.samples.PrivateClass")), argThat(new IsMeasure(CoreMetrics.UNCOVERED_LINES))); verify(context) @@ -221,7 +198,7 @@ public class CoberturaSensorTest { verify(context).saveMeasure( eq(new JavaFile("org.apache.commons.chain.impl.CatalogBase")), argThat(new IsMeasure(CoreMetrics.COVERAGE_LINE_HITS_DATA, - "111=18;121=0;122=0;125=0;126=0;127=0;128=0;131=0;133=0;48=117;56=234;66=0;67=0;68=0;84=999;86=999;98=318"))); + "48=117;56=234;66=0;67=0;68=0;84=999;86=999;98=318;111=18;121=0;122=0;125=0;126=0;127=0;128=0;131=0;133=0"))); } @Test diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java b/sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java index f80e487d868..97942ef8f30 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java @@ -29,7 +29,6 @@ import org.sonar.api.database.model.Snapshot; import org.sonar.api.measures.Measure; import org.sonar.api.measures.Metric; import org.sonar.api.measures.RuleMeasure; -import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; import org.sonar.api.resources.ResourceUtils; import org.sonar.api.rules.Rule; @@ -90,12 +89,12 @@ public final class MeasurePersister { return measure.getId() == null && metric.isOptimizedBestValue() == Boolean.TRUE && metric.getBestValue() != null && - (measure.getValue()==null || NumberUtils.compare(metric.getBestValue(), measure.getValue()) == 0) && + (measure.getValue() == null || NumberUtils.compare(metric.getBestValue(), measure.getValue()) == 0) && measure.getAlertStatus() == null && measure.getDescription() == null && measure.getTendency() == null && measure.getUrl() == null && - measure.getData() == null && + !measure.hasData() && (measure.getVariation1() == null || NumberUtils.compare(measure.getVariation1().doubleValue(), 0.0) == 0) && (measure.getVariation2() == null || NumberUtils.compare(measure.getVariation2().doubleValue(), 0.0) == 0) && (measure.getVariation3() == null || NumberUtils.compare(measure.getVariation3().doubleValue(), 0.0) == 0) && diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java index 0b5c68efb46..d7c94b4853b 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java @@ -19,15 +19,13 @@ */ package org.sonar.api.measures; +import com.google.common.collect.Lists; import org.apache.commons.lang.StringUtils; import org.sonar.api.resources.Scopes; import org.sonar.api.utils.SonarException; import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Set; /** * @since 1.10 @@ -43,6 +41,7 @@ public final class CoreMetrics { public static final String DOMAIN_COMPLEXITY = "Complexity"; public static final String DOMAIN_DOCUMENTATION = "Documentation"; public static final String DOMAIN_RULES = "Rules"; + public static final String DOMAIN_SCM = "SCM"; /** * @deprecated since 2.5 See http://jira.codehaus.org/browse/SONAR-2007 @@ -180,7 +179,12 @@ public final class CoreMetrics { "Commented lines of code", Metric.ValueType.INT, Metric.DIRECTION_WORST, true, DOMAIN_DOCUMENTATION).setFormula( new SumChildValuesFormula(false)).setBestValue(0.0).setOptimizedBestValue(true); - /* unit tests */ + + + + + // UNIT TESTS + public static final String TESTS_KEY = "tests"; public static final Metric TESTS = new Metric(TESTS_KEY, "Unit tests", "Number of unit tests", Metric.ValueType.INT, Metric.DIRECTION_WORST, false, DOMAIN_TESTS); @@ -214,33 +218,83 @@ public final class CoreMetrics { public static final Metric LINES_TO_COVER = new Metric(LINES_TO_COVER_KEY, "Lines to cover", "Lines to cover", Metric.ValueType.INT, Metric.DIRECTION_BETTER, false, DOMAIN_TESTS).setFormula(new SumChildValuesFormula(false)).setHidden(true); + public static final String UNCOVERED_LINES_KEY = "uncovered_lines"; - public static final Metric UNCOVERED_LINES = new Metric(UNCOVERED_LINES_KEY, "Uncovered lines", "Uncovered lines", Metric.ValueType.INT, - Metric.DIRECTION_WORST, false, DOMAIN_TESTS).setFormula(new SumChildValuesFormula(false)); + public static final Metric UNCOVERED_LINES = new Metric.Builder(UNCOVERED_LINES_KEY, Metric.ValueType.INT) + .setName("Uncovered lines") + .setDescription("Uncovered lines") + .setDirection(Metric.DIRECTION_WORST) + .setDomain(DOMAIN_TESTS) + .setFormula(new SumChildValuesFormula(false)) + .create(); + + public static final String NEW_UNCOVERED_LINES_KEY = "new_uncovered_lines"; + public static final Metric NEW_UNCOVERED_LINES = new Metric.Builder(NEW_UNCOVERED_LINES_KEY, Metric.ValueType.INT) + .setName("New uncovered lines") + .setDescription("New uncovered lines") + .setDirection(Metric.DIRECTION_WORST) + .setDomain(DOMAIN_TESTS) + .setFormula(new SumChildValuesFormula(false)) + .create(); public static final String LINE_COVERAGE_KEY = "line_coverage"; public static final Metric LINE_COVERAGE = new Metric(LINE_COVERAGE_KEY, "Line coverage", "Line coverage", Metric.ValueType.PERCENT, Metric.DIRECTION_BETTER, true, DOMAIN_TESTS); public static final String COVERAGE_LINE_HITS_DATA_KEY = "coverage_line_hits_data"; - public static final Metric COVERAGE_LINE_HITS_DATA = new Metric(COVERAGE_LINE_HITS_DATA_KEY, "Coverage hits data", - "Code coverage line hits data", Metric.ValueType.DATA, Metric.DIRECTION_NONE, false, DOMAIN_TESTS); + public static final Metric COVERAGE_LINE_HITS_DATA = new Metric.Builder(COVERAGE_LINE_HITS_DATA_KEY, Metric.ValueType.DATA) + .setDomain(DOMAIN_TESTS) + .create(); public static final String CONDITIONS_TO_COVER_KEY = "conditions_to_cover"; public static final Metric CONDITIONS_TO_COVER = new Metric(CONDITIONS_TO_COVER_KEY, "Conditions to cover", "Conditions to cover", Metric.ValueType.INT, Metric.DIRECTION_BETTER, false, DOMAIN_TESTS).setFormula(new SumChildValuesFormula(false)).setHidden(true); public static final String UNCOVERED_CONDITIONS_KEY = "uncovered_conditions"; - public static final Metric UNCOVERED_CONDITIONS = new Metric(UNCOVERED_CONDITIONS_KEY, "Uncovered conditions", "Uncovered conditions", - Metric.ValueType.INT, Metric.DIRECTION_WORST, false, DOMAIN_TESTS).setFormula(new SumChildValuesFormula(false)); + public static final Metric UNCOVERED_CONDITIONS = new Metric.Builder(UNCOVERED_CONDITIONS_KEY, Metric.ValueType.INT) + .setName("Uncovered conditions") + .setDescription("Uncovered conditions") + .setDirection(Metric.DIRECTION_WORST) + .setDomain(DOMAIN_TESTS) + .setFormula(new SumChildValuesFormula(false)) + .create(); + + public static final String NEW_UNCOVERED_CONDITIONS_KEY = "new_uncovered_conditions"; + public static final Metric NEW_UNCOVERED_CONDITIONS = new Metric.Builder(NEW_UNCOVERED_CONDITIONS_KEY, Metric.ValueType.INT) + .setName("New uncovered conditions") + .setDescription("New uncovered conditions") + .setDirection(Metric.DIRECTION_WORST) + .setDomain(DOMAIN_TESTS) + .setFormula(new SumChildValuesFormula(false)) + .create(); public static final String BRANCH_COVERAGE_KEY = "branch_coverage"; public static final Metric BRANCH_COVERAGE = new Metric(BRANCH_COVERAGE_KEY, "Branch coverage", "Branch coverage", Metric.ValueType.PERCENT, Metric.DIRECTION_BETTER, true, DOMAIN_TESTS).setWorstValue(0.0).setBestValue(100.0); public static final String BRANCH_COVERAGE_HITS_DATA_KEY = "branch_coverage_hits_data"; - public static final Metric BRANCH_COVERAGE_HITS_DATA = new Metric(BRANCH_COVERAGE_HITS_DATA_KEY, "Branch coverage hits", - "Branch coverage hits", Metric.ValueType.DATA, Metric.DIRECTION_NONE, false, DOMAIN_TESTS); + public static final Metric BRANCH_COVERAGE_HITS_DATA = new Metric.Builder(BRANCH_COVERAGE_HITS_DATA_KEY, Metric.ValueType.DATA) + .setName("Branch coverage hits") + .setDomain(DOMAIN_TESTS) + .create(); + + public static final String CONDITIONS_BY_LINE_DATA_KEY = "conditions_by_line_data"; + + /** + * @since 2.7 + */ + public static final Metric CONDITIONS_BY_LINE_DATA = new Metric.Builder(CONDITIONS_BY_LINE_DATA_KEY, Metric.ValueType.DATA) + .setDomain(DOMAIN_TESTS) + .create(); + + public static final String COVERED_CONDITIONS_BY_LINE_DATA_KEY = "covered_conditions_by_line_data"; + + /** + * @since 2.7 + */ + public static final Metric COVERED_CONDITIONS_BY_LINE_DATA = new Metric.Builder(COVERED_CONDITIONS_BY_LINE_DATA_KEY, Metric.ValueType.DATA) + .setDomain(DOMAIN_TESTS) + .create(); /** * @deprecated replaced since 1.11 by UNCOVERED_LINES and UNCOVERED_CONDITIONS @@ -398,7 +452,11 @@ public final class CoreMetrics { public static final Metric NEW_INFO_VIOLATIONS = new Metric(NEW_INFO_VIOLATIONS_KEY, "New Info violations", "New Info violations", Metric.ValueType.INT, Metric.DIRECTION_WORST, true, DOMAIN_RULES).setHidden(true).setBestValue(0.0).setOptimizedBestValue(true); - /* Design */ + + + + + // DESIGN public static final String ABSTRACTNESS_KEY = "abstractness"; public static final Metric ABSTRACTNESS = new Metric(ABSTRACTNESS_KEY, "Abstractness", "Abstractness", Metric.ValueType.PERCENT, @@ -506,9 +564,28 @@ public final class CoreMetrics { public static final Metric PROFILE = new Metric(PROFILE_KEY, "Profile", "Selected quality profile", Metric.ValueType.DATA, Metric.DIRECTION_NONE, false, DOMAIN_GENERAL); - public static List metrics = new ArrayList(); - public static Set metricKeys = new HashSet(); + + + + // SCM + + public static final String SCM_AUTHORS_BY_LINE_KEY = "blame_authors_data";//"scm_authors_by_line"; + public static final Metric SCM_AUTHORS_BY_LINE = new Metric.Builder(SCM_AUTHORS_BY_LINE_KEY, Metric.ValueType.DATA) + .setDomain(DOMAIN_SCM) + .create(); + + public static final String SCM_REVISIONS_BY_LINE_KEY = "blame_revision_data";//"scm_revisions_by_line"; + public static final Metric SCM_REVISIONS_BY_LINE = new Metric.Builder(SCM_REVISIONS_BY_LINE_KEY, Metric.ValueType.DATA) + .setDomain(DOMAIN_SCM) + .create(); + + public static final String SCM_LAST_UPDATE_DATETIME_BY_LINE_KEY = "blame_date_data";//"scm_last_update_datetime_by_line"; + public static final Metric SCM_LAST_UPDATE_DATETIME_BY_LINE = new Metric.Builder(SCM_LAST_UPDATE_DATETIME_BY_LINE_KEY, Metric.ValueType.DATA) + .setDomain(DOMAIN_SCM) + .create(); + + public static List metrics = Lists.newLinkedList(); public static List getMetrics() { if (metrics.isEmpty()) { diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoverageMeasuresBuilder.java b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoverageMeasuresBuilder.java new file mode 100644 index 00000000000..dfc37496a54 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoverageMeasuresBuilder.java @@ -0,0 +1,161 @@ +/* + * 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.api.measures; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.sonar.api.utils.KeyValueFormat; + +import java.util.*; + +/** + * @since 2.7 + */ +public final class CoverageMeasuresBuilder { + + /** + * Metrics of generated measures + */ + public static final List METRICS = Arrays.asList( + CoreMetrics.LINES_TO_COVER, CoreMetrics.UNCOVERED_LINES, CoreMetrics.COVERAGE_LINE_HITS_DATA, + CoreMetrics.CONDITIONS_TO_COVER, CoreMetrics.UNCOVERED_CONDITIONS, CoreMetrics.CONDITIONS_BY_LINE_DATA, + CoreMetrics.COVERED_CONDITIONS_BY_LINE_DATA, CoreMetrics.BRANCH_COVERAGE_HITS_DATA); + + + private int totalCoveredLines = 0, totalConditions = 0, totalCoveredConditions = 0; + private SortedMap hitsByLine = Maps.newTreeMap(); + private SortedMap conditionsByLine = Maps.newTreeMap(); + private SortedMap coveredConditionsByLine = Maps.newTreeMap(); + + private CoverageMeasuresBuilder() { + // use the factory + } + + public CoverageMeasuresBuilder reset() { + totalCoveredLines = 0; + totalConditions = 0; + totalCoveredConditions = 0; + hitsByLine.clear(); + conditionsByLine.clear(); + coveredConditionsByLine.clear(); + return this; + } + + public CoverageMeasuresBuilder setHits(int lineId, int hits) { + if (hitsByLine.containsKey(lineId)) { + throw new IllegalArgumentException("Line " + lineId + " is count twice (hits=" + hits + ")"); + } + hitsByLine.put(lineId, hits); + if (hits > 0) { + totalCoveredLines += 1; + } + return this; + } + + public CoverageMeasuresBuilder setConditions(int lineId, int conditions, int coveredConditions) { + if (conditionsByLine.containsKey(lineId)) { + throw new IllegalArgumentException("Line " + lineId + " is count twice (conditions=" + conditions + ")"); + } + if (conditions > 0) { + totalConditions += conditions; + totalCoveredConditions += coveredConditions; + conditionsByLine.put(lineId, conditions); + coveredConditionsByLine.put(lineId, coveredConditions); + } + return this; + } + + public int getCoveredLines() { + return totalCoveredLines; + } + + public int getLinesToCover() { + return hitsByLine.size(); + } + + public int getConditions() { + return totalConditions; + } + + public int getCoveredConditions() { + return totalCoveredConditions; + } + + public SortedMap getHitsByLine() { + return Collections.unmodifiableSortedMap(hitsByLine); + } + + public SortedMap getConditionsByLine() { + return Collections.unmodifiableSortedMap(conditionsByLine); + } + + public SortedMap getCoveredConditionsByLine() { + return Collections.unmodifiableSortedMap(coveredConditionsByLine); + } + + public Collection createMeasures() { + Collection measures = Lists.newArrayList(); + if (getLinesToCover() > 0) { + measures.add(new Measure(CoreMetrics.LINES_TO_COVER, (double) getLinesToCover())); + measures.add(new Measure(CoreMetrics.UNCOVERED_LINES, (double) (getLinesToCover() - getCoveredLines()))); + measures.add(new Measure(CoreMetrics.COVERAGE_LINE_HITS_DATA).setData(KeyValueFormat.format(hitsByLine)).setPersistenceMode(PersistenceMode.DATABASE)); + } + if (getConditions() > 0) { + measures.add(new Measure(CoreMetrics.CONDITIONS_TO_COVER, (double) getConditions())); + measures.add(new Measure(CoreMetrics.UNCOVERED_CONDITIONS, (double) (getConditions() - getCoveredConditions()))); + measures.add(createConditionsByLineData()); + measures.add(createCoveredConditionsByLineData()); + measures.add(createBranchCoverageByLine()); + } + return measures; + } + + private Measure createCoveredConditionsByLineData() { + return new Measure(CoreMetrics.COVERED_CONDITIONS_BY_LINE_DATA) + .setData(KeyValueFormat.format(coveredConditionsByLine)) + .setPersistenceMode(PersistenceMode.DATABASE); + } + + private Measure createConditionsByLineData() { + return new Measure(CoreMetrics.CONDITIONS_BY_LINE_DATA) + .setData(KeyValueFormat.format(conditionsByLine)) + .setPersistenceMode(PersistenceMode.DATABASE); + } + + private Measure createBranchCoverageByLine() { + PropertiesBuilder builder = new PropertiesBuilder(CoreMetrics.BRANCH_COVERAGE_HITS_DATA); + for (Map.Entry entry : conditionsByLine.entrySet()) { + Integer lineId = entry.getKey(); + int conditions = entry.getValue(); + int coveredConditions = coveredConditionsByLine.get(lineId); + builder.add(lineId, formatBranchCoverage(conditions, coveredConditions)); + } + return builder.build().setPersistenceMode(PersistenceMode.DATABASE); + } + + static String formatBranchCoverage(int conditions, int coveredConditions) { + long branchCoverage = Math.round(100.0 * coveredConditions / conditions); + return branchCoverage + "%"; + } + + public static CoverageMeasuresBuilder create() { + return new CoverageMeasuresBuilder(); + } +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/measures/Measure.java b/sonar-plugin-api/src/main/java/org/sonar/api/measures/Measure.java index e27d13cf69e..3b64b89da3f 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/measures/Measure.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/measures/Measure.java @@ -28,7 +28,7 @@ import java.util.Date; /** * A class to handle measures. - * + * * @since 1.10 */ public class Measure { @@ -60,7 +60,7 @@ public class Measure { /** * Creates a measure with a metric - * + * * @param metric the metric */ public Measure(Metric metric) { @@ -70,9 +70,9 @@ public class Measure { /** * Creates a measure with a metric and a value - * + * * @param metric the metric - * @param value its value + * @param value its value */ public Measure(Metric metric, Double value) { this.metric = metric; @@ -82,9 +82,9 @@ public class Measure { /** * Creates a measure with a metric, a value and a precision for the value - * - * @param metric the metric - * @param value its value + * + * @param metric the metric + * @param value its value * @param precision the value precision */ public Measure(Metric metric, Double value, int precision) { @@ -95,10 +95,10 @@ public class Measure { /** * Creates a measure with a metric, a value and a data field - * + * * @param metric the metric - * @param value the value - * @param data the data field + * @param value the value + * @param data the data field */ public Measure(Metric metric, Double value, String data) { this.metric = metric; @@ -109,9 +109,9 @@ public class Measure { /** * * Creates a measure with a metric and a data field - * + * * @param metric the metric - * @param data the data field + * @param data the data field */ public Measure(Metric metric, String data) { this.metric = metric; @@ -121,9 +121,9 @@ public class Measure { /** * Creates a measure with a metric and an alert level - * + * * @param metric the metric - * @param level the alert level + * @param level the alert level */ public Measure(Metric metric, Metric.Level level) { this.metric = metric; @@ -156,7 +156,7 @@ public class Measure { * example, a measure save in memory at the module level will not be accessible by the root project. In that case, database should be * used. *

- * + * * @param mode the mode * @return the measure object instance */ @@ -178,7 +178,7 @@ public class Measure { /** * Set the underlying metric - * + * * @param metric the metric * @return the measure object instance */ @@ -198,6 +198,10 @@ public class Measure { return null; } + public boolean hasData() { + return data != null; + } + /** * @return the date of the measure, i.e. the date the measure was taken. Used only in TimeMachine queries */ @@ -207,7 +211,7 @@ public class Measure { /** * Sets the date of the measure - Used only in TimeMachine queries - * + * * @param date the date * @return the measure object instance */ @@ -235,7 +239,7 @@ public class Measure { /** * Sets the measure value with the default precision of 1 - * + * * @param v the measure value * @return the measure object instance */ @@ -245,7 +249,7 @@ public class Measure { /** * Sets the measure value as an int - * + * * @param i the value * @return the measure object instance */ @@ -260,8 +264,8 @@ public class Measure { /** * Sets the measure value with a given precision - * - * @param v the measure value + * + * @param v the measure value * @param precision the measure value precision * @return the measure object instance */ @@ -291,7 +295,7 @@ public class Measure { /** * Sets the data field of the measure. - * + * * @param s the data * @return the measure object instance */ @@ -305,7 +309,7 @@ public class Measure { /** * Sets an alert level as the data field - * + * * @param level the alert level * @return the measure object instance */ @@ -327,7 +331,7 @@ public class Measure { /** * Sets the measure description - * + * * @param description the description * @return the measure object instance */ @@ -345,7 +349,7 @@ public class Measure { /** * Set the alert status of the measure - * + * * @param status the status * @return the measure object instance */ @@ -363,7 +367,7 @@ public class Measure { /** * Sets the text associated to the alert on the measure - * + * * @param alertText the text * @return the measure object instance */ @@ -374,7 +378,7 @@ public class Measure { /** * Gets the measure tendency - * + * * @return the tendency */ public Integer getTendency() { @@ -383,7 +387,7 @@ public class Measure { /** * Sets the tendency for the measure - Internal use only - * + * * @param tendency the tendency * @return the measure object instance */ @@ -401,7 +405,7 @@ public class Measure { /** * Sets the measure id - Internal use only - * + * * @param id the id * @return the measure object instance */ @@ -420,7 +424,7 @@ public class Measure { /** * Internal use only - * + * * @since 2.5 */ public Measure setVariation1(Double d) { @@ -438,7 +442,7 @@ public class Measure { /** * Internal use only - * + * * @since 2.5 */ public Measure setVariation2(Double d) { @@ -456,7 +460,7 @@ public class Measure { /** * Internal use only - * + * * @since 2.5 */ public Measure setVariation3(Double d) { @@ -522,7 +526,7 @@ public class Measure { /** * Internal use only - * + * * @since 2.5 */ public Measure setVariation(int index, Double d) { @@ -557,7 +561,7 @@ public class Measure { /** * Sets the URL of the measure - * + * * @param url the url * @return the measure object instance */ diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/measures/Metric.java b/sonar-plugin-api/src/main/java/org/sonar/api/measures/Metric.java index 781b63eef13..92050c92d81 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/measures/Metric.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/measures/Metric.java @@ -105,7 +105,7 @@ public class Metric implements ServerExtension, BatchExtension { @Column(name = "origin", updatable = true, nullable = true, length = 3) @Enumerated(EnumType.STRING) - private Origin origin; + private Origin origin = Origin.JAV; @Column(name = "worst_value", updatable = true, nullable = true, precision = 30, scale = 20) private Double worstValue; @@ -119,7 +119,6 @@ public class Metric implements ServerExtension, BatchExtension { @Column(name = "hidden", updatable = true, nullable = true) private Boolean hidden = Boolean.FALSE; - /** * Creates an empty metric */ @@ -216,6 +215,23 @@ public class Metric implements ServerExtension, BatchExtension { } } + private Metric(String key, String name, ValueType type, String description, Integer direction, String domain, Boolean qualitative, Double worstValue, Double bestValue, Boolean optimizedBestValue, Boolean hidden, Formula formula) { + this.key = key; + this.name = name; + this.description = description; + this.type = type; + this.direction = direction; + this.domain = domain; + this.qualitative = qualitative; + this.userManaged = Boolean.FALSE; + this.enabled = Boolean.TRUE; + this.worstValue = worstValue; + this.optimizedBestValue = optimizedBestValue; + this.bestValue = bestValue; + this.hidden = hidden; + this.formula = formula; + } + /** * For internal use only */ @@ -554,4 +570,81 @@ public class Metric implements ServerExtension, BatchExtension { this.hidden = with.hidden; return this; } + + public static final class Builder { + private String key; + private Metric.ValueType type; + private String name; + private String description; + private Integer direction = DIRECTION_NONE; + private Boolean qualitative = Boolean.FALSE; + private String domain = null; + private Formula formula; + private Double worstValue; + private Double bestValue; + private boolean optimizedBestValue = false; + private boolean hidden = false; + + public Builder(String key, ValueType type) { + this.key = key; + this.type = type; + } + + public Builder setName(String s) { + this.name = s; + return this; + } + + public Builder setDescription(String s) { + this.description = s; + return this; + } + + /** + * Used for numeric values only + */ + public Builder setDirection(Integer i) { + this.direction = i; + return this; + } + + public Builder setQualitative(Boolean b) { + this.qualitative = b; + return this; + } + + public Builder setDomain(String s) { + this.domain = s; + return this; + } + + public Builder setFormula(Formula f) { + this.formula = f; + return this; + } + + public Builder setWorstValue(Double d) { + this.worstValue = d; + return this; + } + + public Builder setBestValue(Double d) { + this.bestValue = d; + return this; + } + + public Builder setOptimizedBestValue(boolean b) { + this.optimizedBestValue = b; + return this; + } + + public Builder setHidden(boolean b) { + this.hidden = b; + return this; + } + + public Metric create() { + return new Metric(key, name, type, description, direction, domain, qualitative, worstValue, bestValue, optimizedBestValue, hidden, formula); + } + } } diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/measures/CoverageMeasuresBuilderTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/measures/CoverageMeasuresBuilderTest.java new file mode 100644 index 00000000000..12c43709c37 --- /dev/null +++ b/sonar-plugin-api/src/test/java/org/sonar/api/measures/CoverageMeasuresBuilderTest.java @@ -0,0 +1,128 @@ +/* + * 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.api.measures; + +import org.junit.Test; + +import java.util.Collection; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +public class CoverageMeasuresBuilderTest { + + @Test + public void shouldNotCreateIfNoValues() { + CoverageMeasuresBuilder builder = CoverageMeasuresBuilder.create(); + assertThat(builder.createMeasures().size(), is(0)); + } + + @Test + public void shouldCreateHitsByLineData() { + CoverageMeasuresBuilder builder = CoverageMeasuresBuilder.create(); + builder.setHits(1, 0); + builder.setHits(2, 3); + builder.setHits(4, 2); + assertThat(find(builder.createMeasures(), CoreMetrics.COVERAGE_LINE_HITS_DATA_KEY).getData(), is("1=0;2=3;4=2")); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldFailIfDuplicatedLineHits() { + CoverageMeasuresBuilder builder = CoverageMeasuresBuilder.create(); + builder.setHits(1, 0); + builder.setHits(1, 3); + } + + @Test + public void shouldCreateUncoveredLines() { + CoverageMeasuresBuilder builder = CoverageMeasuresBuilder.create(); + builder.setHits(1, 0); + builder.setHits(2, 3); + builder.setHits(3, 0); + assertThat(find(builder.createMeasures(), CoreMetrics.UNCOVERED_LINES_KEY).getIntValue(), is(2)); + } + + @Test + public void shouldCreateConditionsByLineData() { + CoverageMeasuresBuilder builder = CoverageMeasuresBuilder.create(); + builder.setConditions(1, 2, 2); + builder.setConditions(2, 1, 0); + assertThat(find(builder.createMeasures(), CoreMetrics.CONDITIONS_BY_LINE_DATA_KEY).getData(), is("1=2;2=1")); + assertThat(find(builder.createMeasures(), CoreMetrics.COVERED_CONDITIONS_BY_LINE_DATA_KEY).getData(), is("1=2;2=0")); + assertThat(find(builder.createMeasures(), CoreMetrics.BRANCH_COVERAGE_HITS_DATA_KEY).getData(), is("1=100%;2=0%")); + } + + @Test + public void shouldCreateNumberOfConditionsToCover() { + CoverageMeasuresBuilder builder = CoverageMeasuresBuilder.create(); + builder.setConditions(1, 2, 2); + builder.setConditions(2, 1, 0); + assertThat(find(builder.createMeasures(), CoreMetrics.CONDITIONS_TO_COVER_KEY).getIntValue(), is(3)); + } + + @Test + public void shouldCreateNumberOfUncoveredConditions() { + CoverageMeasuresBuilder builder = CoverageMeasuresBuilder.create(); + builder.setConditions(1, 2, 2); + builder.setConditions(2, 1, 0); + builder.setConditions(3, 3, 1); + assertThat(find(builder.createMeasures(), CoreMetrics.UNCOVERED_CONDITIONS_KEY).getIntValue(), is(3)); + } + + @Test + public void shouldSetOnlyPositiveConditions() { + CoverageMeasuresBuilder builder = CoverageMeasuresBuilder.create(); + builder.setConditions(1, 0, 0); + builder.setConditions(2, 1, 0); + assertThat(find(builder.createMeasures(), CoreMetrics.CONDITIONS_BY_LINE_DATA_KEY).getData(), is("2=1")); + assertThat(find(builder.createMeasures(), CoreMetrics.COVERED_CONDITIONS_BY_LINE_DATA_KEY).getData(), is("2=0")); + assertThat(find(builder.createMeasures(), CoreMetrics.BRANCH_COVERAGE_HITS_DATA_KEY).getData(), is("2=0%")); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldFailIfDuplicatedLineConditions() { + CoverageMeasuresBuilder builder = CoverageMeasuresBuilder.create(); + builder.setConditions(1, 3, 0); + builder.setConditions(1, 3, 1); + } + + @Test + public void shouldResetFields() { + CoverageMeasuresBuilder builder = CoverageMeasuresBuilder.create(); + builder.setHits(1, 4); + builder.setConditions(1, 3, 1); + builder.reset(); + assertThat(builder.getConditions(), is(0)); + assertThat(builder.getCoveredConditions(), is(0)); + assertThat(builder.getCoveredLines(), is(0)); + assertThat(builder.getHitsByLine().size(), is(0)); + assertThat(builder.getConditionsByLine().size(), is(0)); + assertThat(builder.getCoveredConditionsByLine().size(), is(0)); + } + + private Measure find(Collection measures, String metricKey) { + for (Measure measure : measures) { + if (metricKey.equals(measure.getMetricKey())) { + return measure; + } + } + return null; + } +} diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/measures/MetricTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/measures/MetricTest.java new file mode 100644 index 00000000000..84706bcc658 --- /dev/null +++ b/sonar-plugin-api/src/test/java/org/sonar/api/measures/MetricTest.java @@ -0,0 +1,59 @@ +/* + * 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.api.measures; + +import org.junit.Test; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNull.nullValue; +import static org.junit.Assert.assertThat; + +public class MetricTest { + + @Test + public void shouldCreateMetric() { + Metric metric = new Metric.Builder("foo", Metric.ValueType.INT) + .setDomain("my domain") + .setName("Foo") + .create(); + + assertThat(metric.getKey(), is("foo")); + assertThat(metric.getName(), is("Foo")); + assertThat(metric.getDomain(), is("my domain")); + } + + @Test + public void shouldCreateMetricWithDefaultValues() { + Metric metric = new Metric.Builder("foo", Metric.ValueType.INT) + .create(); + + assertThat(metric.getBestValue(), nullValue()); + assertThat(metric.getDescription(), nullValue()); + assertThat(metric.getWorstValue(), nullValue()); + assertThat(metric.getDirection(), is(Metric.DIRECTION_NONE)); + assertThat(metric.getEnabled(), is(true)); + assertThat(metric.getOrigin(), is(Metric.Origin.JAV)); + assertThat(metric.getFormula(), nullValue()); + assertThat(metric.getId(), nullValue()); + assertThat(metric.getUserManaged(), is(false)); + assertThat(metric.isHidden(), is(false)); + assertThat(metric.isOptimizedBestValue(), is(false)); + } +} -- 2.39.5