Skip to content
36 changes: 24 additions & 12 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<artifactId>mutation-analysis-plugin</artifactId>
<groupId>ch.devcon5.sonar</groupId>
<packaging>sonar-plugin</packaging>
<version>1.5</version>
<version>1.6-SNAPSHOT</version>

<name>${project.artifactId}</name>
<description>Sonar Plugin for integrating and visualizing mutation analysis results</description>
Expand Down Expand Up @@ -63,28 +63,29 @@
</licenses>

<properties>
<sonar.buildVersion>6.7</sonar.buildVersion>
<sonar.build.version>8.9.2.46101</sonar.build.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jdk.min.version>1.8</jdk.min.version>
<kotlin.version>1.2.61</kotlin.version>
<junit.version>4.13.1</junit.version>
<slf4j.version>1.7.6</slf4j.version>
<log4j.version>2.13.3</log4j.version>
<mockito.version>2.21.0</mockito.version>
<jacoco.version>0.8.4</jacoco.version>
<pitest.version>1.4.9</pitest.version>
<kotlin.version>1.5.31</kotlin.version>
<junit.version>4.13.2</junit.version>
<staxmate.version>2.0.1</staxmate.version>
<slf4j.version>1.7.32</slf4j.version>
<log4j.version>2.14.1</log4j.version>
<mockito.version>2.28.2</mockito.version>
<jacoco.version>0.8.7</jacoco.version>
<pitest.version>1.7.2</pitest.version>
<!-- license settings -->
<license.owner>DevCon5 GmbH</license.owner>
<license.title>Mutation Analysis Plugin</license.title>
<license.years>2015-2016</license.years>
<license.years>2015-2021</license.years>
<license.mailto>info@devcon5.ch</license.mailto>
</properties>

<dependencies>
<dependency>
<groupId>org.sonarsource.sonarqube</groupId>
<artifactId>sonar-plugin-api</artifactId>
<version>${sonar.buildVersion}</version>
<version>${sonar.build.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
Expand All @@ -97,6 +98,11 @@
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.staxmate</groupId>
<artifactId>staxmate</artifactId>
<version>${staxmate.version}</version>
</dependency>
<!-- Logging Binding for Test output -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
Expand All @@ -117,10 +123,16 @@
<scope>test</scope>
</dependency>
<!-- unit tests -->
<dependency>
<groupId>org.sonarsource.sonarqube</groupId>
<artifactId>sonar-plugin-api-impl</artifactId>
<version>${sonar.build.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.sonarsource.sonarqube</groupId>
<artifactId>sonar-testing-harness</artifactId>
<version>${sonar.buildVersion}</version>
<version>${sonar.build.version}</version>
<scope>test</scope>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -574,8 +574,9 @@ public Builder usingMutator(final String mutagenName) {
if (mutationOperator == MutationOperators.UNKNOWN) {
LOGGER.warn("Found unknown mutation operator: {}", mutagenName);
mutatorSuffix = "";
} else if (mutagenName.startsWith(mutationOperator.getClassName())) {
mutatorSuffix = mutagenName.substring(mutationOperator.getClassName().length());
} else if (mutationOperator.getClassNames().stream().anyMatch(mutagenName::startsWith)) {
final String mutatorClassName = mutationOperator.getClassNames().stream().filter(mutagenName::startsWith).findAny().get();
mutatorSuffix = mutagenName.substring(mutatorClassName.length());
} else {
mutatorSuffix = "";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@

import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -46,11 +51,11 @@ public final class MutationOperator {
private final URL mutagenDescLoc;
private final String violationDesc;
private final String name;
private final String className;
private final Set<String> classNames;

MutationOperator(final String id,
final String name,
final String className,
final Collection<String> classNames,
final String violationDesc,
final URL mutagenDescriptionLocation) {

Expand All @@ -59,7 +64,7 @@ public final class MutationOperator {
requireNonNull(violationDesc, "violation description must not be null");
this.id = id;
this.name = name;
this.className = className;
this.classNames = new HashSet<>(requireNonNull(classNames, "classNames must not be null"));
this.violationDesc = violationDesc;
this.mutagenDescLoc = mutagenDescriptionLocation;
}
Expand Down Expand Up @@ -111,13 +116,12 @@ public String getName() {
}

/**
* The fully qualified classname of the {@link MutationOperator} class.
* The fully qualified classnames of the {@link MutationOperator} class.
*
* @return the classname
* @return the classnames
*/
public String getClassName() {

return className;
public Set<String> getClassNames() {
return Collections.unmodifiableSet(classNames);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
Expand All @@ -41,7 +43,7 @@
*
*/
public final class MutationOperators {

private MutationOperators(){}

/**
Expand All @@ -53,7 +55,7 @@ private MutationOperators(){}
*/
public static final MutationOperator UNKNOWN = new MutationOperator("UNKNOWN",
"Unknown mutagen",
"unknown.mutation.operator",
Collections.singleton("unknown.mutation.operator"),
"An unknown mutagen has been applied",
null);
/**
Expand Down Expand Up @@ -97,13 +99,20 @@ private static MutationOperator toMutagen(final Node mutagenNode) throws XPathEx

final XPath xp = XPATH_FACTORY.get().newXPath();
final String id = xp.evaluate("@id", mutagenNode);
final String className = xp.evaluate("@class", mutagenNode);

final Set<String> classNames = new HashSet<>();
final NodeList classNodes = (NodeList) xp.evaluate("classes/class", mutagenNode, NODESET);
for (int i = 0, len = classNodes.getLength(); i < len; i++) {
final Node classNode = classNodes.item(i);
classNames.add(classNode.getTextContent());
}

final String name = xp.evaluate("name", mutagenNode);
final String violationDescription = xp.evaluate("violationDescription", mutagenNode).trim();
final URL mutagenDescLoc = MutationOperator.class.getResource(xp.evaluate("operatorDescription/@classpath",
mutagenNode));

return new MutationOperator(id, name, className, violationDescription, mutagenDescLoc);
return new MutationOperator(id, name, classNames, violationDescription, mutagenDescLoc);
}

/**
Expand All @@ -120,7 +129,7 @@ public static MutationOperator find(final String mutagenKey) {
MutationOperator result = UNKNOWN;
for (final MutationOperator mutationOperator : INSTANCES.values()) {
if (mutagenKey.equals(mutationOperator.getId())
|| mutagenKey.startsWith(mutationOperator.getClassName())) {
|| mutationOperator.getClassNames().stream().anyMatch(mutagenKey::startsWith)) {
result = mutationOperator;
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
import java.util.Collections;

import ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant;
import com.ctc.wstx.exc.WstxParsingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -137,7 +136,7 @@ Collection<Mutant> readMutants(final InputStream stream) throws XMLStreamExcepti
final XMLStreamReader reader = inf.createXMLStreamReader(stream);
try {
return readMutants(reader);
} catch (IllegalArgumentException | WstxParsingException e){
} catch (IllegalArgumentException e){
throw new XMLStreamException(e.getMessage(), reader.getLocation(),e);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
import org.sonar.api.batch.rule.ActiveRule;
import org.sonar.api.batch.rule.ActiveRules;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.issue.NewIssue;
import org.sonar.api.batch.sensor.issue.NewIssueLocation;
import org.sonar.api.batch.sensor.issue.internal.DefaultIssueLocation;
import org.sonar.api.config.Configuration;

/**
Expand Down Expand Up @@ -75,7 +75,9 @@ public void processRules(final Collection<ResourceMutationMetrics> metrics, fina

if (activeRules.isEmpty()) {
// ignore violations from report, if rule not activated in Sonar
LOG.warn("/!\\ At least one Mutation Analysis rule needs to be activated the current profile.");
LOG.warn(
"/!\\ At least one Mutation Analysis rule needs to be activated for the current profile and language: {}.",
language);
}

metrics.stream()
Expand All @@ -84,7 +86,7 @@ public void processRules(final Collection<ResourceMutationMetrics> metrics, fina
}

/**
* Applies the active rules on resource metrics for the {@link org.sonar.api.issue.Issuable} resource.
* Applies the active rules on resource metrics.
*
* @param resourceMetrics
* the mutants for found for the issuable
Expand All @@ -104,7 +106,7 @@ private void applyRules(final ResourceMutationMetrics resourceMetrics, final Col
* Applies the active rule on the issuable if any of the resource metrics for the issuable violates the rule
*
* @param resourceMetrics
* the metrics for the {@link org.sonar.api.resources.Resource} behind the {@link org.sonar.api.issue.Issuable}
* the metrics for the Resource
* @param rule
* the active rule to apply
* @param context
Expand Down Expand Up @@ -146,11 +148,10 @@ private void applyThresholdRule(final ResourceMutationMetrics resourceMetrics, f
final double minimumKilledMutants = resourceMetrics.getMutationsTotal() * threshold / 100.0;
final double additionalRequiredMutants = Math.ceil(minimumKilledMutants - resourceMetrics.getMutationsKilled());

context.newIssue()
.forRule(rule.ruleKey())
.gap(settings.getDouble(MutationAnalysisPlugin.EFFORT_FACTOR_MISSING_COVERAGE).orElse(1.0) * additionalRequiredMutants)
.at(newLocation().on(resourceMetrics.getResource()).message(generateThresholdViolationMessage(actualCoverage, threshold, additionalRequiredMutants)))
.save();
NewIssue newIssue = context.newIssue().forRule(rule.ruleKey());
newIssue.gap(settings.getDouble(MutationAnalysisPlugin.EFFORT_FACTOR_MISSING_COVERAGE).orElse(1.0) * additionalRequiredMutants)
.at(newIssue.newLocation().on(resourceMetrics.getResource()).message(generateThresholdViolationMessage(actualCoverage, threshold, additionalRequiredMutants)))
.save();
}
}

Expand Down Expand Up @@ -198,13 +199,15 @@ private void applyMutantRule(final ResourceMutationMetrics resourceMetrics, fina
|| violatesUnknownMutantStatusRule(rule, mutant)
|| violatesMutatorRule(rule, mutant)) {

context.newIssue()
.forRule(rule.ruleKey())
.gap(settings.getDouble(MutationAnalysisPlugin.EFFORT_FACTOR_SURVIVED_MUTANT).orElse(1.0))
.at(newLocation().on(resourceMetrics.getResource())
.at(resourceMetrics.getResource().selectLine(mutant.getLineNumber()))
.message(getViolationDescription(mutant)))
.save();
NewIssue newIssue = context.newIssue().forRule(rule.ruleKey());

NewIssueLocation newLocation = newIssue.newLocation().on(resourceMetrics.getResource())
.at(resourceMetrics.getResource().selectLine(mutant.getLineNumber()))
.message(getViolationDescription(mutant));

newIssue.gap(settings.getDouble(MutationAnalysisPlugin.EFFORT_FACTOR_SURVIVED_MUTANT).orElse(1.0))
.at(newLocation)
.save();
}
}
}
Expand Down Expand Up @@ -296,8 +299,4 @@ private String getViolationDescription(final Mutant mutant) {
return message.toString();
}

private static NewIssueLocation newLocation() {

return new DefaultIssueLocation();
}
}
Loading