diff options
author | simonbrandhof <simon.brandhof@gmail.com> | 2010-09-06 14:08:06 +0000 |
---|---|---|
committer | simonbrandhof <simon.brandhof@gmail.com> | 2010-09-06 14:08:06 +0000 |
commit | aeadc1f9129274949daaa57738c7c4550bdfbc7b (patch) | |
tree | 08dadf5ef7474fc41d1d48f74648f1ba8b55f34d /plugins/sonar-cpd-plugin | |
download | sonarqube-aeadc1f9129274949daaa57738c7c4550bdfbc7b.tar.gz sonarqube-aeadc1f9129274949daaa57738c7c4550bdfbc7b.zip |
SONAR-236 remove deprecated code from checkstyle plugin + display default value of rule parameters in Q profile console
Diffstat (limited to 'plugins/sonar-cpd-plugin')
13 files changed, 1239 insertions, 0 deletions
diff --git a/plugins/sonar-cpd-plugin/pom.xml b/plugins/sonar-cpd-plugin/pom.xml new file mode 100644 index 00000000000..f15ec0e824d --- /dev/null +++ b/plugins/sonar-cpd-plugin/pom.xml @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar</artifactId> + <version>2.3-SNAPSHOT</version> + <relativePath>../..</relativePath> + </parent> + <groupId>org.codehaus.sonar.plugins</groupId> + <artifactId>sonar-cpd-plugin</artifactId> + <name>Sonar :: Plugins :: CPD</name> + <packaging>sonar-plugin</packaging> + <description>Find duplicated source code within project.</description> + + <dependencies> + <dependency> + <groupId>pmd</groupId> + <artifactId>pmd</artifactId> + <version>4.2.5</version> + <exclusions> + <exclusion> + <groupId>jaxen</groupId> + <artifactId>jaxen</artifactId> + </exclusion> + <exclusion> + <groupId>ant</groupId> + <artifactId>ant</artifactId> + </exclusion> + <exclusion> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-duplications</artifactId> + </dependency> + <dependency> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-plugin-api</artifactId> + </dependency> + <dependency> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-testing-harness</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <testResources> + <testResource> + <directory>${basedir}/src/main/resources</directory> + </testResource> + <testResource> + <directory>${basedir}/src/test/resources</directory> + </testResource> + </testResources> + <plugins> + <plugin> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-packaging-maven-plugin</artifactId> + <extensions>true</extensions> + <configuration> + <pluginKey>cpd</pluginKey> + <pluginName>Duplications</pluginName> + <pluginClass>org.sonar.plugins.cpd.CpdPlugin</pluginClass> + </configuration> + </plugin> + <plugin> + <artifactId>maven-deploy-plugin</artifactId> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> +</project>
\ No newline at end of file diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdAnalyser.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdAnalyser.java new file mode 100644 index 00000000000..4d1da130abb --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdAnalyser.java @@ -0,0 +1,150 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2009 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * Sonar is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Sonar is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.plugins.cpd; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import net.sourceforge.pmd.cpd.TokenEntry; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.CpdMapping; +import org.sonar.api.batch.SensorContext; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.duplications.cpd.Match; + +public class CpdAnalyser { + + private static final Logger LOG = LoggerFactory.getLogger(CpdAnalyser.class); + + private CpdMapping mapping; + private SensorContext context; + private Project project; + + public CpdAnalyser(Project project, SensorContext context, CpdMapping mapping) { + this.mapping = mapping; + this.context = context; + this.project = project; + } + + public void analyse(Iterator<Match> matches) { + Map<Resource, DuplicationsData> duplicationsData = new HashMap<Resource, DuplicationsData>(); + while (matches.hasNext()) { + Match match = matches.next(); + + for (TokenEntry firstMark : match.getMarkSet()) { + String firstAbsolutePath = firstMark.getTokenSrcID(); + int firstLine = firstMark.getBeginLine(); + + Resource firstFile = mapping.createResource(new File(firstAbsolutePath), project.getFileSystem().getSourceDirs()); + if (firstFile == null) { + LOG.warn("CPD - File not found : {}", firstAbsolutePath); + continue; + } + + for (TokenEntry tokenEntry : match.getMarkSet()) { + String secondAbsolutePath = tokenEntry.getTokenSrcID(); + int secondLine = tokenEntry.getBeginLine(); + if (secondAbsolutePath.equals(firstAbsolutePath) && firstLine == secondLine) { + continue; + } + Resource secondFile = mapping.createResource(new File(secondAbsolutePath), project.getFileSystem().getSourceDirs()); + if (secondFile == null) { + LOG.warn("CPD - File not found : {}", secondAbsolutePath); + continue; + } + + processClassMeasure(duplicationsData, firstFile, firstLine, secondFile, secondLine, match.getLineCount()); + } + } + } + + for (DuplicationsData data : duplicationsData.values()) { + data.saveUsing(context); + } + } + + private void processClassMeasure(Map<Resource, DuplicationsData> fileContainer, Resource file, int duplicationStartLine, + Resource targetFile, int targetDuplicationStartLine, int duplicatedLines) { + if (file != null && targetFile != null) { + DuplicationsData data = fileContainer.get(file); + if (data == null) { + data = new DuplicationsData(file, context); + fileContainer.put(file, data); + } + data.cumulate(targetFile, targetDuplicationStartLine, duplicationStartLine, duplicatedLines); + } + } + + private static final class DuplicationsData { + + protected Set<Integer> duplicatedLines = new HashSet<Integer>(); + protected double duplicatedBlocks; + protected Resource resource; + private SensorContext context; + private List<StringBuilder> duplicationXMLEntries = new ArrayList<StringBuilder>(); + + private DuplicationsData(Resource resource, SensorContext context) { + this.context = context; + this.resource = resource; + } + + protected void cumulate(Resource targetResource, int targetDuplicationStartLine, int duplicationStartLine, int duplicatedLines) { + StringBuilder xml = new StringBuilder(); + xml.append("<duplication lines=\"").append(duplicatedLines).append("\" start=\"").append(duplicationStartLine).append( + "\" target-start=\"").append(targetDuplicationStartLine).append("\" target-resource=\"").append( + context.saveResource(targetResource)).append("\"/>"); + + duplicationXMLEntries.add(xml); + + int duplicatedLinesBefore = this.duplicatedLines.size(); + for (int duplicatedLine = duplicationStartLine; duplicatedLine < duplicationStartLine + duplicatedLines; duplicatedLine++) { + this.duplicatedLines.add(duplicatedLine); + } + this.duplicatedBlocks++; + } + + protected void saveUsing(SensorContext context) { + context.saveMeasure(resource, CoreMetrics.DUPLICATED_FILES, 1d); + context.saveMeasure(resource, CoreMetrics.DUPLICATED_LINES, (double) duplicatedLines.size()); + context.saveMeasure(resource, CoreMetrics.DUPLICATED_BLOCKS, duplicatedBlocks); + context.saveMeasure(resource, new Measure(CoreMetrics.DUPLICATIONS_DATA, getDuplicationXMLData())); + } + + private String getDuplicationXMLData() { + StringBuilder duplicationXML = new StringBuilder("<duplications>"); + for (StringBuilder xmlEntry : duplicationXMLEntries) { + duplicationXML.append(xmlEntry); + } + duplicationXML.append("</duplications>"); + return duplicationXML.toString(); + } + } +} diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdException.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdException.java new file mode 100644 index 00000000000..389a4c59c08 --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdException.java @@ -0,0 +1,26 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2009 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * Sonar is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Sonar is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.plugins.cpd; + +public class CpdException extends RuntimeException { + public CpdException(Throwable throwable) { + super(throwable); + } +} diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java new file mode 100644 index 00000000000..e4a8c54a4ad --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java @@ -0,0 +1,86 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2009 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * Sonar is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Sonar is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.plugins.cpd; + +import org.sonar.api.*; +import org.sonar.plugins.cpd.decorators.DuplicationDensityDecorator; +import org.sonar.plugins.cpd.decorators.SumDuplicationsDecorator; + +import java.util.ArrayList; +import java.util.List; + +@Properties({ + @Property( + key = CoreProperties.CPD_MINIMUM_TOKENS_PROPERTY, + defaultValue = CoreProperties.CPD_MINIMUM_TOKENS_DEFAULT_VALUE + "", + name = "Minimum tokens", + description = "The number of duplicate tokens above which a block is considered as a duplication.", + project = true, + module = true, + global = true), + @Property( + key = CoreProperties.CPD_IGNORE_LITERALS_PROPERTY, + defaultValue = CoreProperties.CPD_IGNORE_LITERALS_DEFAULT_VALUE + "", + name = "Ignore literals", + description = "if true, CPD ignores literal value differences when evaluating a duplicate block. " + + "This means that foo=\"first string\"; and foo=\"second string\"; will be seen as equivalent.", + project = true, + module = true, + global = true), + @Property( + key = CoreProperties.CPD_IGNORE_IDENTIFIERS_PROPERTY, + defaultValue = CoreProperties.CPD_IGNORE_IDENTIFIERS_DEFAULT_VALUE + "", + name = "Ignore identifiers", + description = "Similar to 'Ignore literals' but for identifiers; i.e., variable names, methods names, and so forth.", + project = true, + module = true, + global = true), + @Property( + key = CoreProperties.CPD_SKIP_PROPERTY, + defaultValue = "false", + name = "Skip detection of duplicated code", + description = "Searching for duplicated code is memory hungry therefore for very big projects it can be necessary to turn the functionality off.", + project = true, + module = true, + global = true) +}) +public class CpdPlugin implements Plugin { + + public String getKey() { + return CoreProperties.CPD_PLUGIN; + } + + public String getName() { + return "Duplications"; + } + + public String getDescription() { + return "Find duplicated source code within project."; + } + + public List<Class<? extends Extension>> getExtensions() { + List<Class<? extends Extension>> list = new ArrayList<Class<? extends Extension>>(); + list.add(CpdSensor.class); + list.add(SumDuplicationsDecorator.class); + list.add(DuplicationDensityDecorator.class); + list.add(JavaCpdMapping.class); + return list; + } +}
\ No newline at end of file diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java new file mode 100644 index 00000000000..db232383622 --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java @@ -0,0 +1,123 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2009 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * Sonar is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Sonar is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.plugins.cpd; + +import net.sourceforge.pmd.cpd.AbstractLanguage; +import net.sourceforge.pmd.cpd.TokenEntry; +import org.apache.commons.configuration.Configuration; +import org.slf4j.LoggerFactory; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.CpdMapping; +import org.sonar.api.batch.Sensor; +import org.sonar.api.batch.SensorContext; +import org.sonar.api.resources.Language; +import org.sonar.api.resources.Project; +import org.sonar.duplications.cpd.CPD; + +import java.io.IOException; +import java.nio.charset.Charset; + +public class CpdSensor implements Sensor { + + private CpdMapping[] mappings; + + public CpdSensor(CpdMapping[] mappings) { + this.mappings = mappings; + } + + public boolean shouldExecuteOnProject(Project project) { + CpdMapping mapping = getMapping(project.getLanguage()); + if (mapping == null) { + LoggerFactory.getLogger(getClass()).info("Detection of duplication code is not supported for {}.", project.getLanguage()); + return false; + } + + if (isSkipped(project)) { + LoggerFactory.getLogger(getClass()).info("Detection of duplicated code is skipped"); + return false; + } + + return true; + } + + boolean isSkipped(Project project) { + Configuration conf = project.getConfiguration(); + return conf.getBoolean("sonar.cpd." + project.getLanguageKey() + ".skip", + conf.getBoolean("sonar.cpd.skip", false)); + } + + public void analyse(Project project, SensorContext context) { + CpdMapping mapping = getMapping(project.getLanguage()); + CPD cpd = executeCPD(project, mapping, project.getFileSystem().getSourceCharset()); + saveResults(cpd, mapping, project, context); + } + + private CpdMapping getMapping(Language language) { + for (CpdMapping cpdMapping : mappings) { + if (cpdMapping.getLanguage().equals(language)) { + return cpdMapping; + } + } + return null; + } + + private void saveResults(CPD cpd, CpdMapping mapping, Project project, SensorContext context) { + CpdAnalyser cpdAnalyser = new CpdAnalyser(project, context, mapping); + cpdAnalyser.analyse(cpd.getMatches()); + } + + private CPD executeCPD(Project project, CpdMapping mapping, Charset encoding) { + try { + CPD cpd = configureCPD(project, mapping, encoding); + cpd.go(); + return cpd; + + } catch (Exception e) { + throw new CpdException(e); + } + } + + private CPD configureCPD(Project project, CpdMapping mapping, Charset encoding) throws IOException { + // To avoid a cpd bug generating error as "java.lang.IndexOutOfBoundsException: Index: 259, Size: 248" + // See http://sourceforge.net/tracker/?func=detail&atid=479921&aid=1947823&group_id=56262 for more details + TokenEntry.clearImages(); + + int minTokens = getMinimumTokens(project); + AbstractLanguage cpdLanguage = new AbstractLanguage(mapping.getTokenizer()) { + }; + + CPD cpd = new CPD(minTokens, cpdLanguage); + cpd.setEncoding(encoding.name()); + cpd.setLoadSourceCodeSlices(false); + cpd.add(project.getFileSystem().getSourceFiles(project.getLanguage())); + return cpd; + } + + int getMinimumTokens(Project project) { + Configuration conf = project.getConfiguration(); + return conf.getInt("sonar.cpd." + project.getLanguageKey() + ".minimumTokens", + conf.getInt("sonar.cpd.minimumTokens", CoreProperties.CPD_MINIMUM_TOKENS_DEFAULT_VALUE)); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } +} diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/JavaCpdMapping.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/JavaCpdMapping.java new file mode 100644 index 00000000000..5faa5ab2a1d --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/JavaCpdMapping.java @@ -0,0 +1,65 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2009 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * Sonar is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Sonar is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.plugins.cpd; + +import java.io.File; +import java.util.List; +import java.util.Properties; + +import net.sourceforge.pmd.cpd.JavaTokenizer; +import net.sourceforge.pmd.cpd.Tokenizer; + +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.CpdMapping; +import org.sonar.api.resources.Java; +import org.sonar.api.resources.JavaFile; +import org.sonar.api.resources.Language; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; + +public class JavaCpdMapping implements CpdMapping { + + private String ignore_literals; + private String ignore_identifiers; + + public JavaCpdMapping(Project project) { + ignore_literals = project.getConfiguration().getString(CoreProperties.CPD_IGNORE_LITERALS_PROPERTY, + CoreProperties.CPD_IGNORE_LITERALS_DEFAULT_VALUE); + ignore_identifiers = project.getConfiguration().getString(CoreProperties.CPD_IGNORE_IDENTIFIERS_PROPERTY, + CoreProperties.CPD_IGNORE_IDENTIFIERS_DEFAULT_VALUE); + } + + public Tokenizer getTokenizer() { + Properties props = new Properties(); + props.setProperty(JavaTokenizer.IGNORE_LITERALS, ignore_literals); + props.setProperty(JavaTokenizer.IGNORE_IDENTIFIERS, ignore_identifiers); + JavaTokenizer tokenizer = new JavaTokenizer(); + tokenizer.setProperties(props); + return tokenizer; + } + + public Resource createResource(File file, List<File> sourceDirs) { + return JavaFile.fromIOFile(file, sourceDirs, false); + } + + public Language getLanguage() { + return Java.INSTANCE; + } +} diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/decorators/DuplicationDensityDecorator.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/decorators/DuplicationDensityDecorator.java new file mode 100644 index 00000000000..c05835cfb13 --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/decorators/DuplicationDensityDecorator.java @@ -0,0 +1,90 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2009 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * Sonar is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Sonar is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.plugins.cpd.decorators; + +import org.sonar.api.batch.Decorator; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.batch.DependedUpon; +import org.sonar.api.batch.DependsUpon; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.Metric; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; + +import java.util.Arrays; +import java.util.List; + +public class DuplicationDensityDecorator implements Decorator { + + @DependsUpon + public List<Metric> dependsUponMetrics() { + return Arrays.asList( + CoreMetrics.NCLOC, + CoreMetrics.COMMENT_LINES, + CoreMetrics.DUPLICATED_LINES, + CoreMetrics.LINES); + } + + @DependedUpon + public Metric generatesMetric() { + return CoreMetrics.DUPLICATED_LINES_DENSITY; + } + + public boolean shouldExecuteOnProject(Project project) { + return true; + } + + public void decorate(Resource resource, DecoratorContext context) { + Measure nbDuplicatedLines = context.getMeasure(CoreMetrics.DUPLICATED_LINES); + if (nbDuplicatedLines == null) { + return; + } + + Double divisor = getNbLinesFromLocOrNcloc(context); + if (divisor != null && divisor > 0.0) { + context.saveMeasure(CoreMetrics.DUPLICATED_LINES_DENSITY, calculate(nbDuplicatedLines.getValue(), divisor)); + } + } + + private Double getNbLinesFromLocOrNcloc(DecoratorContext context) { + Measure nbLoc = context.getMeasure(CoreMetrics.LINES); + if (nbLoc != null) { + // TODO test this branch + return nbLoc.getValue(); + } + Measure nbNcloc = context.getMeasure(CoreMetrics.NCLOC); + if (nbNcloc != null) { + Measure nbComments = context.getMeasure(CoreMetrics.COMMENT_LINES); + Double nbLines = nbNcloc.getValue(); + return nbComments != null ? nbLines + nbComments.getValue() : nbLines; + } + return null; + } + + protected Double calculate(Double dividend, Double divisor) { + Double result = 100.0 * dividend / divisor; + if (result < 100.0) { + return result; + } + return 100.0; + } + +} diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/decorators/SumDuplicationsDecorator.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/decorators/SumDuplicationsDecorator.java new file mode 100644 index 00000000000..c47b18a7110 --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/decorators/SumDuplicationsDecorator.java @@ -0,0 +1,55 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2009 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * Sonar is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Sonar is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.plugins.cpd.decorators; + +import org.sonar.api.batch.AbstractSumChildrenDecorator; +import org.sonar.api.batch.DependedUpon; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Metric; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.api.resources.ResourceUtils; + +import java.util.Arrays; +import java.util.List; + +public class SumDuplicationsDecorator extends AbstractSumChildrenDecorator { + + @Override + @DependedUpon + public List<Metric> generatesMetrics() { + return Arrays.asList(CoreMetrics.DUPLICATED_BLOCKS, CoreMetrics.DUPLICATED_FILES, CoreMetrics.DUPLICATED_LINES); + } + + @Override + protected boolean shouldSaveZeroIfNoChildMeasures() { + return true; + } + + @Override + public boolean shouldExecuteOnProject(Project project) { + return true; + } + + @Override + public boolean shouldDecorateResource(Resource resource) { + return !ResourceUtils.isUnitTestClass(resource); + } +} diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdAnalyserTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdAnalyserTest.java new file mode 100644 index 00000000000..cc4e4681e85 --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdAnalyserTest.java @@ -0,0 +1,281 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2009 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * Sonar is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Sonar is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.plugins.cpd; + +import static org.mockito.Matchers.anyList; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.argThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +import net.sourceforge.pmd.cpd.TokenEntry; + +import org.junit.Test; +import org.sonar.api.batch.CpdMapping; +import org.sonar.api.batch.SensorContext; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.resources.JavaFile; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.ProjectFileSystem; +import org.sonar.api.resources.Resource; +import org.sonar.api.test.IsMeasure; +import org.sonar.duplications.cpd.Match; + +public class CpdAnalyserTest { + + @Test + public void testOneSimpleDuplicationBetweenTwoFiles() throws Exception { + ProjectFileSystem fileSystem = mock(ProjectFileSystem.class); + when(fileSystem.getSourceDirs()).thenReturn(Collections.<File> emptyList()); + File file1 = new File("target/tmp/file1.ext"); + File file2 = new File("target/tmp/file2.ext"); + + Project project = new Project("key").setFileSystem(fileSystem); + + SensorContext context = mock(SensorContext.class); + + CpdMapping cpdMapping = mock(CpdMapping.class); + Resource resource1 = new JavaFile("foo.Foo"); + Resource resource2 = new JavaFile("foo.Bar"); + when(cpdMapping.createResource((File) anyObject(), anyList())).thenReturn(resource1).thenReturn(resource2).thenReturn(resource2) + .thenReturn(resource1); + when(context.saveResource(resource1)).thenReturn("key1"); + when(context.saveResource(resource2)).thenReturn("key2"); + + Match match1 = new Match(5, new TokenEntry(null, file1.getAbsolutePath(), 5), new TokenEntry(null, file2.getAbsolutePath(), 15)); + match1.setLineCount(200); + + CpdAnalyser cpdAnalyser = new CpdAnalyser(project, context, cpdMapping); + cpdAnalyser.analyse(Arrays.asList(match1).iterator()); + + verify(context).saveMeasure(resource1, CoreMetrics.DUPLICATED_FILES, 1d); + verify(context).saveMeasure(resource1, CoreMetrics.DUPLICATED_BLOCKS, 1d); + verify(context).saveMeasure(resource1, CoreMetrics.DUPLICATED_LINES, 200d); + verify(context).saveMeasure( + eq(resource1), + argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications>" + + "<duplication lines=\"200\" start=\"5\" target-start=\"15\" target-resource=\"key2\"/>" + "</duplications>"))); + + verify(context).saveMeasure(resource2, CoreMetrics.DUPLICATED_FILES, 1d); + verify(context).saveMeasure(resource2, CoreMetrics.DUPLICATED_LINES, 200d); + verify(context).saveMeasure(resource2, CoreMetrics.DUPLICATED_BLOCKS, 1d); + verify(context).saveMeasure( + eq(resource2), + argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, + "<duplications><duplication lines=\"200\" start=\"15\" target-start=\"5\" target-resource=\"key1\"/></duplications>"))); + + verify(context, atLeastOnce()).saveResource(resource1); + verify(context, atLeastOnce()).saveResource(resource2); + } + + @Test + public void testClassicalCaseWithTwoDuplicatedBlocsInvolvingThreeFiles() throws Exception { + ProjectFileSystem fileSystem = mock(ProjectFileSystem.class); + when(fileSystem.getSourceDirs()).thenReturn(Collections.<File> emptyList()); + File file1 = new File("target/tmp/file1.ext"); + File file2 = new File("target/tmp/file2.ext"); + File file3 = new File("target/tmp/file3.ext"); + + Project project = new Project("key").setFileSystem(fileSystem); + + SensorContext context = mock(SensorContext.class); + + CpdMapping cpdMapping = mock(CpdMapping.class); + Resource resource1 = new JavaFile("foo.Foo"); + Resource resource2 = new JavaFile("foo.Bar"); + Resource resource3 = new JavaFile("foo.Hotel"); + when(cpdMapping.createResource((File) anyObject(), anyList())).thenReturn(resource1).thenReturn(resource2).thenReturn(resource2) + .thenReturn(resource1).thenReturn(resource1).thenReturn(resource3).thenReturn(resource3).thenReturn(resource1); + when(context.saveResource(resource1)).thenReturn("key1"); + when(context.saveResource(resource2)).thenReturn("key2"); + when(context.saveResource(resource3)).thenReturn("key3"); + + Match match1 = new Match(5, new TokenEntry(null, file1.getAbsolutePath(), 5), new TokenEntry(null, file2.getAbsolutePath(), 15)); + match1.setLineCount(200); + Match match2 = new Match(5, new TokenEntry(null, file1.getAbsolutePath(), 5), new TokenEntry(null, file3.getAbsolutePath(), 15)); + match2.setLineCount(100); + + CpdAnalyser cpdAnalyser = new CpdAnalyser(project, context, cpdMapping); + cpdAnalyser.analyse(Arrays.asList(match1, match2).iterator()); + + verify(context).saveMeasure(resource1, CoreMetrics.DUPLICATED_FILES, 1d); + verify(context).saveMeasure(resource1, CoreMetrics.DUPLICATED_BLOCKS, 2d); + verify(context).saveMeasure(resource1, CoreMetrics.DUPLICATED_LINES, 200d); + verify(context).saveMeasure( + eq(resource1), + argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications>" + + "<duplication lines=\"200\" start=\"5\" target-start=\"15\" target-resource=\"key2\"/>" + + "<duplication lines=\"100\" start=\"5\" target-start=\"15\" target-resource=\"key3\"/>" + "</duplications>"))); + + verify(context).saveMeasure(resource2, CoreMetrics.DUPLICATED_FILES, 1d); + verify(context).saveMeasure(resource2, CoreMetrics.DUPLICATED_LINES, 200d); + verify(context).saveMeasure(resource2, CoreMetrics.DUPLICATED_BLOCKS, 1d); + verify(context).saveMeasure( + eq(resource2), + argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, + "<duplications><duplication lines=\"200\" start=\"15\" target-start=\"5\" target-resource=\"key1\"/></duplications>"))); + + verify(context).saveMeasure(resource3, CoreMetrics.DUPLICATED_FILES, 1d); + verify(context).saveMeasure(resource3, CoreMetrics.DUPLICATED_LINES, 100d); + verify(context).saveMeasure(resource3, CoreMetrics.DUPLICATED_BLOCKS, 1d); + verify(context).saveMeasure( + eq(resource3), + argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, + "<duplications><duplication lines=\"100\" start=\"15\" target-start=\"5\" target-resource=\"key1\"/></duplications>"))); + + verify(context, atLeastOnce()).saveResource(resource1); + verify(context, atLeastOnce()).saveResource(resource2); + verify(context, atLeastOnce()).saveResource(resource3); + } + + @Test + public void testOneDuplicatedBlocInvolvingMoreThanTwoFiles() throws Exception { + ProjectFileSystem fileSystem = mock(ProjectFileSystem.class); + when(fileSystem.getSourceDirs()).thenReturn(Collections.<File> emptyList()); + File file1 = new File("target/tmp/file1.ext"); + File file2 = new File("target/tmp/file2.ext"); + File file3 = new File("target/tmp/file3.ext"); + File file4 = new File("target/tmp/file4.ext"); + + Project project = new Project("key").setFileSystem(fileSystem); + + SensorContext context = mock(SensorContext.class); + + CpdMapping cpdMapping = mock(CpdMapping.class); + Resource resource1 = new JavaFile("foo.Foo"); + Resource resource2 = new JavaFile("foo.Bar"); + Resource resource3 = new JavaFile("foo.Hotel"); + Resource resource4 = new JavaFile("foo.Coffee"); + when(cpdMapping.createResource((File) anyObject(), anyList())).thenReturn(resource1).thenReturn(resource2).thenReturn(resource3) + .thenReturn(resource4).thenReturn(resource2).thenReturn(resource1).thenReturn(resource3).thenReturn(resource4) + .thenReturn(resource3).thenReturn(resource1).thenReturn(resource2).thenReturn(resource4).thenReturn(resource4) + .thenReturn(resource1).thenReturn(resource2).thenReturn(resource3); + when(context.saveResource(resource1)).thenReturn("key1"); + when(context.saveResource(resource2)).thenReturn("key2"); + when(context.saveResource(resource3)).thenReturn("key3"); + when(context.saveResource(resource4)).thenReturn("key4"); + + Match match = new Match(5, createTokenEntry(file1.getAbsolutePath(), 5), createTokenEntry(file2.getAbsolutePath(), 15)); + match.setLineCount(200); + Set<TokenEntry> tokenEntries = new LinkedHashSet<TokenEntry>(); + tokenEntries.add(createTokenEntry(file1.getAbsolutePath(), 5)); + tokenEntries.add(createTokenEntry(file2.getAbsolutePath(), 15)); + tokenEntries.add(createTokenEntry(file3.getAbsolutePath(), 7)); + tokenEntries.add(createTokenEntry(file4.getAbsolutePath(), 10)); + match.setMarkSet(tokenEntries); + + CpdAnalyser cpdAnalyser = new CpdAnalyser(project, context, cpdMapping); + cpdAnalyser.analyse(Arrays.asList(match).iterator()); + + verify(context).saveMeasure(resource1, CoreMetrics.DUPLICATED_FILES, 1d); + verify(context).saveMeasure(resource1, CoreMetrics.DUPLICATED_BLOCKS, 3d); + verify(context).saveMeasure(resource1, CoreMetrics.DUPLICATED_LINES, 200d); + verify(context).saveMeasure( + eq(resource1), + argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications>" + + "<duplication lines=\"200\" start=\"5\" target-start=\"15\" target-resource=\"key2\"/>" + + "<duplication lines=\"200\" start=\"5\" target-start=\"7\" target-resource=\"key3\"/>" + + "<duplication lines=\"200\" start=\"5\" target-start=\"10\" target-resource=\"key4\"/>" + "</duplications>"))); + + verify(context).saveMeasure(resource3, CoreMetrics.DUPLICATED_FILES, 1d); + verify(context).saveMeasure(resource3, CoreMetrics.DUPLICATED_LINES, 200d); + verify(context).saveMeasure(resource3, CoreMetrics.DUPLICATED_BLOCKS, 3d); + verify(context).saveMeasure( + eq(resource2), + argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications>" + + "<duplication lines=\"200\" start=\"15\" target-start=\"5\" target-resource=\"key1\"/>" + + "<duplication lines=\"200\" start=\"15\" target-start=\"7\" target-resource=\"key3\"/>" + + "<duplication lines=\"200\" start=\"15\" target-start=\"10\" target-resource=\"key4\"/>" + "</duplications>"))); + + verify(context).saveMeasure(resource2, CoreMetrics.DUPLICATED_FILES, 1d); + verify(context).saveMeasure(resource2, CoreMetrics.DUPLICATED_LINES, 200d); + verify(context).saveMeasure(resource2, CoreMetrics.DUPLICATED_BLOCKS, 3d); + verify(context).saveMeasure( + eq(resource3), + argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications>" + + "<duplication lines=\"200\" start=\"7\" target-start=\"5\" target-resource=\"key1\"/>" + + "<duplication lines=\"200\" start=\"7\" target-start=\"15\" target-resource=\"key2\"/>" + + "<duplication lines=\"200\" start=\"7\" target-start=\"10\" target-resource=\"key4\"/>" + "</duplications>"))); + + verify(context).saveMeasure(resource4, CoreMetrics.DUPLICATED_LINES, 200d); + verify(context).saveMeasure(resource4, CoreMetrics.DUPLICATED_FILES, 1d); + verify(context).saveMeasure(resource4, CoreMetrics.DUPLICATED_BLOCKS, 3d); + verify(context).saveMeasure( + eq(resource4), + argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications>" + + "<duplication lines=\"200\" start=\"10\" target-start=\"5\" target-resource=\"key1\"/>" + + "<duplication lines=\"200\" start=\"10\" target-start=\"15\" target-resource=\"key2\"/>" + + "<duplication lines=\"200\" start=\"10\" target-start=\"7\" target-resource=\"key3\"/>" + "</duplications>"))); + + verify(context, atLeastOnce()).saveResource(resource1); + verify(context, atLeastOnce()).saveResource(resource2); + verify(context, atLeastOnce()).saveResource(resource3); + verify(context, atLeastOnce()).saveResource(resource4); + } + + @Test + public void testDuplicationOnSameFile() throws Exception { + ProjectFileSystem fileSystem = mock(ProjectFileSystem.class); + when(fileSystem.getSourceDirs()).thenReturn(Collections.<File> emptyList()); + File file1 = new File("target/tmp/file1.ext"); + + Project project = new Project("key").setFileSystem(fileSystem); + + SensorContext context = mock(SensorContext.class); + + CpdMapping cpdMapping = mock(CpdMapping.class); + Resource resource1 = new JavaFile("foo.Foo"); + when(cpdMapping.createResource((File) anyObject(), anyList())).thenReturn(resource1).thenReturn(resource1); + when(context.saveResource(resource1)).thenReturn("key1"); + + Match match1 = new Match(304, new TokenEntry(null, file1.getAbsolutePath(), 5), new TokenEntry(null, file1.getAbsolutePath(), 215)); + match1.setLineCount(200); + + CpdAnalyser cpdAnalyser = new CpdAnalyser(project, context, cpdMapping); + cpdAnalyser.analyse(Arrays.asList(match1).iterator()); + + verify(context).saveMeasure(resource1, CoreMetrics.DUPLICATED_FILES, 1d); + verify(context).saveMeasure(resource1, CoreMetrics.DUPLICATED_BLOCKS, 2d); + verify(context).saveMeasure(resource1, CoreMetrics.DUPLICATED_LINES, 400d); + verify(context).saveMeasure( + eq(resource1), + argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications>" + + "<duplication lines=\"200\" start=\"5\" target-start=\"215\" target-resource=\"key1\"/>" + + "<duplication lines=\"200\" start=\"215\" target-start=\"5\" target-resource=\"key1\"/>" + + "</duplications>"))); + + verify(context, atLeastOnce()).saveResource(resource1); + } + + private static TokenEntry createTokenEntry(String sourceId, int line) { + TokenEntry entry = new TokenEntry(null, sourceId, line); + entry.setHashCode(sourceId.hashCode() + line); + return entry; + } +} diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdPluginTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdPluginTest.java new file mode 100644 index 00000000000..4fa0132b48d --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdPluginTest.java @@ -0,0 +1,32 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2009 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * Sonar is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Sonar is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.plugins.cpd; + +import static org.hamcrest.number.OrderingComparisons.greaterThan; +import static org.junit.Assert.assertThat; +import org.junit.Test; + +public class CpdPluginTest { + + @Test + public void getExtensions() { + assertThat(new CpdPlugin().getExtensions().size(), greaterThan(1)); + } +} diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java new file mode 100644 index 00000000000..3687a26b3a1 --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java @@ -0,0 +1,106 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2009 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * Sonar is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Sonar is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.plugins.cpd; + +import org.apache.commons.configuration.PropertiesConfiguration; +import org.junit.Test; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.CpdMapping; +import org.sonar.api.resources.Project; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +public class CpdSensorTest { + + @Test + public void generalSkip() { + PropertiesConfiguration conf = new PropertiesConfiguration(); + conf.setProperty("sonar.cpd.skip", "true"); + + Project project = createJavaProject().setConfiguration(conf); + + CpdSensor sensor = new CpdSensor(new CpdMapping[0]); + assertTrue(sensor.isSkipped(project)); + } + + @Test + public void doNotSkipByDefault() { + Project project = createJavaProject().setConfiguration(new PropertiesConfiguration()); + + CpdSensor sensor = new CpdSensor(new CpdMapping[0]); + assertFalse(sensor.isSkipped(project)); + } + + @Test + public void skipByLanguage() { + PropertiesConfiguration conf = new PropertiesConfiguration(); + conf.setProperty("sonar.cpd.skip", "false"); + conf.setProperty("sonar.cpd.php.skip", "true"); + + Project phpProject = createPhpProject().setConfiguration(conf); + Project javaProject = createJavaProject().setConfiguration(conf); + + CpdSensor sensor = new CpdSensor(new CpdMapping[0]); + assertTrue(sensor.isSkipped(phpProject)); + assertFalse(sensor.isSkipped(javaProject)); + } + + @Test + public void defaultMinimumTokens() { + Project project = createJavaProject().setConfiguration(new PropertiesConfiguration()); + + CpdSensor sensor = new CpdSensor(new CpdMapping[0]); + assertEquals(CoreProperties.CPD_MINIMUM_TOKENS_DEFAULT_VALUE, sensor.getMinimumTokens(project)); + } + + @Test + public void generalMinimumTokens() { + PropertiesConfiguration conf = new PropertiesConfiguration(); + conf.setProperty("sonar.cpd.minimumTokens", "33"); + Project project = createJavaProject().setConfiguration(conf); + + CpdSensor sensor = new CpdSensor(new CpdMapping[0]); + assertEquals(33, sensor.getMinimumTokens(project)); + } + + @Test + public void minimumTokensByLanguage() { + PropertiesConfiguration conf = new PropertiesConfiguration(); + conf.setProperty("sonar.cpd.minimumTokens", "100"); + conf.setProperty("sonar.cpd.php.minimumTokens", "33"); + + Project phpProject = createPhpProject().setConfiguration(conf); + Project javaProject = createJavaProject().setConfiguration(conf); + + CpdSensor sensor = new CpdSensor(new CpdMapping[0]); + assertEquals(100, sensor.getMinimumTokens(javaProject)); + assertEquals(33, sensor.getMinimumTokens(phpProject)); + } + + private Project createJavaProject() { + return new Project("java_project").setLanguageKey("java"); + } + + private Project createPhpProject() { + return new Project("php_project").setLanguageKey("php"); + } +} diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/decorators/DuplicationDensityDecoratorTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/decorators/DuplicationDensityDecoratorTest.java new file mode 100644 index 00000000000..f958c8549fe --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/decorators/DuplicationDensityDecoratorTest.java @@ -0,0 +1,80 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2009 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * Sonar is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Sonar is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.plugins.cpd.decorators; + +import org.junit.Test; +import static org.mockito.Mockito.*; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; + +public class DuplicationDensityDecoratorTest { + + @Test + public void densityIsBalancedByNclocAndCommentLines() { + DecoratorContext context = mock(DecoratorContext.class); + when(context.getMeasure(CoreMetrics.NCLOC)).thenReturn(new Measure(CoreMetrics.NCLOC, 40.0)); + when(context.getMeasure(CoreMetrics.COMMENT_LINES)).thenReturn(new Measure(CoreMetrics.COMMENT_LINES, 10.0)); + when(context.getMeasure(CoreMetrics.DUPLICATED_LINES)).thenReturn(new Measure(CoreMetrics.DUPLICATED_LINES, 10.0)); + + DuplicationDensityDecorator decorator = new DuplicationDensityDecorator(); + decorator.decorate(null, context); + + verify(context).saveMeasure(CoreMetrics.DUPLICATED_LINES_DENSITY, 20.0); + } + + + @Test + public void densityEvenIfNoComments() { + DecoratorContext context = mock(DecoratorContext.class); + when(context.getMeasure(CoreMetrics.NCLOC)).thenReturn(new Measure(CoreMetrics.NCLOC, 40.0)); + when(context.getMeasure(CoreMetrics.DUPLICATED_LINES)).thenReturn(new Measure(CoreMetrics.DUPLICATED_LINES, 10.0)); + + DuplicationDensityDecorator decorator = new DuplicationDensityDecorator(); + decorator.decorate(null, context); + + verify(context).saveMeasure(CoreMetrics.DUPLICATED_LINES_DENSITY, 25.0); + } + + @Test + public void noDensityIfNoDuplicationMeasure() { + DecoratorContext context = mock(DecoratorContext.class); + when(context.getMeasure(CoreMetrics.NCLOC)).thenReturn(new Measure(CoreMetrics.NCLOC, 45.0)); + + DuplicationDensityDecorator decorator = new DuplicationDensityDecorator(); + decorator.decorate(null, context); + + verify(context, never()).saveMeasure(eq(CoreMetrics.DUPLICATED_LINES_DENSITY), anyDouble()); + } + + @Test + public void noDensityWhenZeroNclocAndComments() { + DecoratorContext context = mock(DecoratorContext.class); + when(context.getMeasure(CoreMetrics.NCLOC)).thenReturn(new Measure(CoreMetrics.NCLOC, 0.0)); + when(context.getMeasure(CoreMetrics.DUPLICATED_LINES)).thenReturn(new Measure(CoreMetrics.COMMENT_LINES, 0.0)); + when(context.getMeasure(CoreMetrics.DUPLICATED_LINES)).thenReturn(new Measure(CoreMetrics.DUPLICATED_LINES, 10.0)); + + DuplicationDensityDecorator decorator = new DuplicationDensityDecorator(); + decorator.decorate(null, context); + + verify(context, never()).saveMeasure(eq(CoreMetrics.DUPLICATED_LINES_DENSITY), anyDouble()); + } + +} diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/decorators/SumDuplicationsDecoratorTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/decorators/SumDuplicationsDecoratorTest.java new file mode 100644 index 00000000000..32cf781e366 --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/decorators/SumDuplicationsDecoratorTest.java @@ -0,0 +1,66 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2009 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * Sonar is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Sonar is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.plugins.cpd.decorators; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.number.OrderingComparisons.greaterThan; +import static org.junit.Assert.assertThat; +import org.junit.Test; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.*; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.resources.JavaFile; +import org.sonar.api.resources.Resource; +import org.sonar.api.test.IsMeasure; + +public class SumDuplicationsDecoratorTest { + + @Test + public void parapets() { + SumDuplicationsDecorator decorator = new SumDuplicationsDecorator(); + assertThat(decorator.generatesMetrics().size(), greaterThan(0)); + assertThat(decorator.shouldSaveZeroIfNoChildMeasures(), is(true)); + } + + @Test + public void doNotSetDuplicationsOnUnitTests() { + SumDuplicationsDecorator decorator = new SumDuplicationsDecorator(); + Resource unitTest = new JavaFile("org.foo.BarTest", true); + DecoratorContext context = mock(DecoratorContext.class); + + decorator.decorate(unitTest, context); + + verify(context, never()).saveMeasure((Measure) anyObject()); + } + + @Test + public void saveZeroIfNoDuplications() { + SumDuplicationsDecorator decorator = new SumDuplicationsDecorator(); + Resource unitTest = new JavaFile("org.foo.BarTest", false); + DecoratorContext context = mock(DecoratorContext.class); + + decorator.decorate(unitTest, context); + + verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.DUPLICATED_LINES, 0.0))); + } +} |