diff --git a/.gitignore b/.gitignore
index f322c19..3fcc4a0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -127,3 +127,4 @@ work
.vscode/
.DS_Store
+.aider*
diff --git a/README.md b/README.md
index 9f8feec..a90feee 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ Configure report path, e.g. `target/pit-reports/**/mutations.xml` for a Maven bu
## Jenkins Pipeline
You can use the following step in pipeline to use this plugin in pipeline:
-`pitmutation killRatioMustImprove: false, minimumKillRatio: 50.0, mutationStatsFile: '**/target/pit-reports/**/mutations.xml'`
+`pitmutation ignoreMissingReports: false, killRatioMustImprove: false, minimumKillRatio: 50.0, mutationStatsFile: '**/target/pit-reports/**/mutations.xml'`
The plugin needs the XML and HTML output from PIT. Also make sure
that a clean target is executed before building, otherwise PIT will
diff --git a/pom.xml b/pom.xml
index 2b709c5..5c0a99f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,14 +9,14 @@
pitmutation
- 1.0-19-SNAPSHOT
+ 2.0.2
Jenkins PIT Mutation Plugin
hpi
http://wiki.jenkins-ci.org/display/JENKINS/pitmutation
- scm:git:ssh://git@github.com/jenkinsci/pitmutation-plugin.git
- scm:git:ssh://git@github.com/jenkinsci/pitmutation-plugin.git
+ scm:git:git://git@github.com/jenkinsci/pitmutation-plugin.git
+ scm:git:git://git@github.com/jenkinsci/pitmutation-plugin.git
https://github.com/jenkinsci/pitmutation-plugin
HEAD
@@ -37,6 +37,12 @@
Benjamin Sproule
benjamin@benjaminsproule.com
+
+ vasilej
+ Vasile Jureschi
+ vasile.jureschi@gmail.com
+
+
@@ -54,6 +60,19 @@
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.14.1
+
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-xml
+ 2.14.1
+
+
org.projectlombok
lombok
@@ -61,11 +80,6 @@
provided
-
- org.apache.commons
- commons-digester3
- 3.2
-
org.jenkins-ci
symbol-annotation
@@ -85,16 +99,16 @@
test
- junit
- junit
- 4.13.2
+ org.mockito
+ mockito-junit-jupiter
+ 5.4.0
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.9.2
test
-
-
- org.hamcrest
- hamcrest-core
-
-
@@ -129,8 +143,8 @@
org.apache.maven.plugins
maven-compiler-plugin
- 11
- 11
+ 17
+ 17
@@ -141,7 +155,7 @@
org.apache.maven.plugins
maven-javadoc-plugin
- 8
+ 17
@@ -164,6 +178,13 @@
org.pitest
pitest-maven
1.16.1
+
+
+ org.pitest
+ pitest-junit5-plugin
+ 1.1.1
+
+
org.jenkinsci.plugins.pitmutation.*
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/DescriptorImpl.java b/src/main/java/org/jenkinsci/plugins/pitmutation/DescriptorImpl.java
new file mode 100644
index 0000000..e7a6cf2
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/DescriptorImpl.java
@@ -0,0 +1,44 @@
+package org.jenkinsci.plugins.pitmutation;
+
+import hudson.Extension;
+import hudson.model.AbstractProject;
+import hudson.tasks.BuildStepDescriptor;
+import hudson.tasks.Publisher;
+import net.sf.json.JSONObject;
+import org.jenkinsci.Symbol;
+import org.kohsuke.stapler.StaplerRequest;
+
+/**
+ * The type Descriptor.
+ */
+@Extension
+@Symbol("pitmutation")
+public class DescriptorImpl extends BuildStepDescriptor {
+
+ /**
+ * Instantiates a new Descriptor.
+ */
+ public DescriptorImpl() {
+ super(PitPublisher.class);
+ }
+
+ @Override
+ public String getDisplayName() {
+ return Messages.PitPublisher_DisplayName();
+ }
+
+ @Override
+ public boolean isApplicable(Class extends AbstractProject> aClass) {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
+ req.bindParameters(this, "pitmutation");
+ save();
+ return super.configure(req, formData);
+ }
+}
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/FileProcessor.java b/src/main/java/org/jenkinsci/plugins/pitmutation/FileProcessor.java
new file mode 100644
index 0000000..1f8891c
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/FileProcessor.java
@@ -0,0 +1,54 @@
+package org.jenkinsci.plugins.pitmutation;
+
+import hudson.FilePath;
+import org.apache.commons.lang.StringUtils;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static java.util.Optional.ofNullable;
+
+public class FileProcessor {
+
+ public static final String SINGLE_MODULE_REPORT_FOLDER = "mutation-report-all";
+ public static final String MULTI_MODULE_REPORT_FORMAT = "mutation-report-%s";
+
+ public void copySingleModuleReport(FilePath source, FilePath buildTarget) throws IOException, InterruptedException {
+ copyMutationReports(source, buildTarget, SINGLE_MODULE_REPORT_FOLDER);
+ }
+
+ public void copyMultiModuleReport(FilePath source, FilePath buildTarget, String module) throws IOException,
+ InterruptedException {
+ copyMutationReports(source, buildTarget, String.format(MULTI_MODULE_REPORT_FORMAT, module));
+ }
+
+ private void copyMutationReports(FilePath source, FilePath buildTarget, String mutationFilePath) throws IOException,
+ InterruptedException {
+ var targetPath = new FilePath(buildTarget, mutationFilePath);
+ var parent = ofNullable(source.getParent()).orElseThrow(() -> new IOException("Mutation file not found"));
+ parent.copyRecursiveTo(targetPath);
+ }
+
+ public Map getNames(FilePath[] reports, String base) {
+ Map names = new HashMap<>();
+ for (int i = 0; i < reports.length; i++) {
+ FilePath report = reports[i];
+
+ final String moduleName;
+ if (StringUtils.isBlank(base)) {
+ moduleName = String.valueOf(i == 0 ? null : i);
+ } else {
+ String[] partsFromRemoteWithoutBase = report.getRemote().replace(base, "").split("[/\\\\]");
+ if (partsFromRemoteWithoutBase.length > 1) {
+ moduleName = partsFromRemoteWithoutBase[1];
+ } else {
+ moduleName = String.valueOf(i == 0 ? null : i);
+ }
+ }
+
+ names.put(report, moduleName);
+ }
+ return names;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/MustImproveCondition.java b/src/main/java/org/jenkinsci/plugins/pitmutation/MustImproveCondition.java
new file mode 100644
index 0000000..ec1ac96
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/MustImproveCondition.java
@@ -0,0 +1,22 @@
+package org.jenkinsci.plugins.pitmutation;
+
+import hudson.model.Result;
+import org.jenkinsci.plugins.pitmutation.targets.MutationStats;
+
+import static hudson.model.Result.SUCCESS;
+import static hudson.model.Result.UNSTABLE;
+
+class MustImproveCondition implements Condition {
+ @Override
+ public Result decideResult(final PitBuildAction action) {
+ PitBuildAction previousAction = action.getPreviousAction();
+ if (previousAction != null) {
+ MutationStats previousStats = previousAction.getReport().getMutationStats();
+ return action.getReport().getMutationStats().getKillPercent() >= previousStats.getKillPercent() ?
+ SUCCESS :
+ UNSTABLE;
+ } else {
+ return SUCCESS;
+ }
+ }
+}
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/Mutation.java b/src/main/java/org/jenkinsci/plugins/pitmutation/Mutation.java
index ff08537..7b2443f 100644
--- a/src/main/java/org/jenkinsci/plugins/pitmutation/Mutation.java
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/Mutation.java
@@ -2,7 +2,12 @@
import lombok.Data;
+import java.util.ArrayList;
+import java.util.List;
+
/**
+ * `
+ *
* @author edward
*/
@Data
@@ -12,19 +17,36 @@ public class Mutation {
private String sourceFile;
private String mutatedClass;
private String mutatedMethod;
+ private int numberOfTestsRun;
private int lineNumber;
private String mutator;
- private int index;
+ /**
+ * @deprecated from 1 .9 the mutation library generates a list of indexes JENKINS-68990
+ */
+ @Deprecated(forRemoval = true, since = "1.9")
+ private Integer index;
+ private List indexes = new ArrayList<>();
private String killingTest;
private String methodDescription;
private String description;
+ /**
+ * @deprecated from 1 .9 the mutation library generates a list of blocks JENKINS-68990
+ */
+ @Deprecated(forRemoval = true, since = "1.9")
private String block;
+ private List blocks = new ArrayList<>();
+
+ public Mutation() {
+ }
public String getMutatorClass() {
int lastDot = mutator.lastIndexOf('.');
String className = mutator.substring(lastDot + 1);
- return className.endsWith("Mutator")
- ? className.substring(0, className.length() - 7)
- : className;
+ return className.endsWith("Mutator") ? className.substring(0, className.length() - 7) : className;
}
+
+ protected boolean canEqual(final Object other) {
+ return other instanceof Mutation;
+ }
+
}
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/MutationReport.java b/src/main/java/org/jenkinsci/plugins/pitmutation/MutationReport.java
index 7597a1f..cf8150d 100644
--- a/src/main/java/org/jenkinsci/plugins/pitmutation/MutationReport.java
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/MutationReport.java
@@ -1,63 +1,49 @@
package org.jenkinsci.plugins.pitmutation;
-import org.apache.commons.digester3.Digester;
+import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule;
+import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import org.jenkinsci.plugins.pitmutation.targets.MutationStats;
import org.xml.sax.SAXException;
import java.io.IOException;
import java.io.InputStream;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
/**
* @author edward
+ * @author vasile.jureschi
*/
public class MutationReport {
private final Map> mutationsByPackage;
+
private final Map> mutationsByClass;
private int killCount = 0;
- public MutationReport() {
+ public MutationReport(InputStream xmlReport) throws IOException, SAXException {
this.mutationsByClass = new HashMap<>();
this.mutationsByPackage = new HashMap<>();
- }
- public static MutationReport create(InputStream xmlReport) throws IOException, SAXException {
- return digestMutations(xmlReport);
- }
+ // https://github.com/FasterXML/jackson-dataformat-xml/issues/219
+ JacksonXmlModule module = new JacksonXmlModule();
+ module.setDefaultUseWrapper(false);
+ XmlMapper xmlMapper = new XmlMapper(module);
+ Mutations mutations = xmlMapper.readValue(xmlReport, Mutations.class);
- private static MutationReport digestMutations(InputStream input) throws IOException, SAXException {
- Digester digester = new Digester();
- digester.addObjectCreate("mutations", MutationReport.class);
- digester.addObjectCreate("mutations/mutation", Mutation.class);
- digester.addSetNext("mutations/mutation", "addMutation", "org.jenkinsci.plugins.pitmutation.Mutation");
- digester.addSetProperties("mutations/mutation");
- digester.addSetNestedProperties("mutations/mutation");
-
- MutationReport report = digester.parse(input);
- report.mutationsByClass.forEach((className, mutations) -> {
- String packageName = packageNameFromClass(className);
- List existingPackageMutations = report.mutationsByPackage
- .computeIfAbsent(packageName, k -> new ArrayList<>());
- mutations.stream().filter(mutation -> !existingPackageMutations.contains(mutation))
- .forEach(mutation -> report.mutationsByPackage.get(packageName).add(mutation));
- });
- return report;
- }
+ mutations.getMutation().forEach(mutation -> {
+ mutationsByClass.computeIfAbsent(mutation.getMutatedClass(), k -> new ArrayList<>()).add(mutation);
+ if (mutation.isDetected()) {
+ killCount++;
+ }
+ mutationsByPackage
+ .computeIfAbsent(packageNameFromClass(mutation.getMutatedClass()), k -> new ArrayList<>())
+ .add(mutation);
- /**
- * Called by digester.
- *
- * @param mutation {@link Mutation} to add
- */
- public void addMutation(Mutation mutation) {
- mutationsByClass.computeIfAbsent(mutation.getMutatedClass(), k -> new ArrayList<>())
- .add(mutation);
- if (mutation.isDetected()) {
- killCount++;
- }
- mutationsByPackage.computeIfAbsent(packageNameFromClass(mutation.getMutatedClass()), k -> new ArrayList<>())
- .add(mutation);
+ });
}
public Collection getMutationsForPackage(String packageName) {
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/Mutations.java b/src/main/java/org/jenkinsci/plugins/pitmutation/Mutations.java
new file mode 100644
index 0000000..9f49f99
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/Mutations.java
@@ -0,0 +1,14 @@
+package org.jenkinsci.plugins.pitmutation;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@AllArgsConstructor
+@Data
+@NoArgsConstructor
+public class Mutations {
+ private List mutation;
+}
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/ParseReportCallable.java b/src/main/java/org/jenkinsci/plugins/pitmutation/ParseReportCallable.java
new file mode 100644
index 0000000..bfbc7a4
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/ParseReportCallable.java
@@ -0,0 +1,41 @@
+package org.jenkinsci.plugins.pitmutation;
+
+import hudson.FilePath;
+import hudson.remoting.VirtualChannel;
+import org.jenkinsci.remoting.RoleChecker;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * The type Parse report callable.
+ */
+public class ParseReportCallable implements FilePath.FileCallable {
+
+ private static final long serialVersionUID = 1L;
+
+ private final String reportFilePath;
+
+ /**
+ * Instantiates a new Parse report callable.
+ *
+ * @param reportFilePath the report file path
+ */
+ public ParseReportCallable(String reportFilePath) {
+ this.reportFilePath = reportFilePath;
+ }
+
+ @Override
+ public FilePath[] invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
+ FilePath[] r = new FilePath(f).list(reportFilePath);
+ if (r.length < 1) {
+ throw new IOException("No reports found at location:" + reportFilePath);
+ }
+ return r;
+ }
+
+ @Override
+ public void checkRoles(RoleChecker roleChecker) throws SecurityException {
+
+ }
+}
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/PercentageThresholdCondition.java b/src/main/java/org/jenkinsci/plugins/pitmutation/PercentageThresholdCondition.java
new file mode 100644
index 0000000..3fd5269
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/PercentageThresholdCondition.java
@@ -0,0 +1,21 @@
+package org.jenkinsci.plugins.pitmutation;
+
+import hudson.model.Result;
+import org.jenkinsci.plugins.pitmutation.targets.MutationStats;
+
+import static hudson.model.Result.FAILURE;
+import static hudson.model.Result.SUCCESS;
+
+class PercentageThresholdCondition implements Condition {
+ private final float percentage;
+
+ PercentageThresholdCondition(float percentage) {
+ super();
+ this.percentage = percentage;
+ }
+
+ @Override
+ public Result decideResult(PitBuildAction action) {
+ return action.getReport().getMutationStats().getKillPercent() >= percentage ? SUCCESS : FAILURE;
+ }
+}
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/PitBuildAction.java b/src/main/java/org/jenkinsci/plugins/pitmutation/PitBuildAction.java
index 998756b..91490a9 100644
--- a/src/main/java/org/jenkinsci/plugins/pitmutation/PitBuildAction.java
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/PitBuildAction.java
@@ -27,12 +27,15 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import static hudson.model.Result.FAILURE;
+
/**
* @author edward
*/
@Slf4j
-public class PitBuildAction implements HealthReportingAction, StaplerProxy {
+public class PitBuildAction implements HealthReportingAction,
+ StaplerProxy {
private static final Pattern MUTATION_REPORT_PATTERN = Pattern.compile(".*mutation-report-([^/\\\\]*).*");
@@ -45,16 +48,13 @@ public PitBuildAction(Run, ?> owner) {
}
public PitBuildAction getPreviousAction() {
- Run, ?> b = owner;
+ Run, ?> build = owner;
while (true) {
- b = b.getPreviousBuild();
- if (b == null)
- return null;
- if (b.getResult() == Result.FAILURE)
- continue;
- PitBuildAction r = b.getAction(PitBuildAction.class);
- if (r != null)
- return r;
+ build = build.getPreviousBuild();
+ if (build == null) {return null;}
+ if (build.getResult() == FAILURE) {continue;}
+ PitBuildAction action = build.getAction(PitBuildAction.class);
+ if (action != null) {return action;}
}
}
@@ -94,7 +94,7 @@ private Map readReports() {
} else {
name = String.valueOf(i);
}
- reports.put(name, MutationReport.create(files[i].read()));
+ reports.put(name, new MutationReport(files[i].read()));
}
} catch (IOException | InterruptedException | SAXException e) {
e.printStackTrace();
@@ -121,7 +121,7 @@ static PitBuildAction getPreviousResult(Run, ?> start) {
if (b == null) {
return null;
}
- assert b.getResult() != Result.FAILURE : "We asked for the previous not failed build";
+ assert b.getResult() != FAILURE : "We asked for the previous not failed build";
PitBuildAction r = b.getAction(PitBuildAction.class);
if (r != null) {
return r;
@@ -132,7 +132,7 @@ static PitBuildAction getPreviousResult(Run, ?> start) {
@Override
public HealthReport getBuildHealth() {
return new HealthReport((int) getReport().getMutationStats().getKillPercent(),
- Messages._BuildAction_Description(getReport().getMutationStats().getKillPercent()));
+ Messages._BuildAction_Description(getReport().getMutationStats().getKillPercent()));
}
@Override
@@ -173,14 +173,14 @@ public void doGraph(StaplerRequest req, StaplerResponse rsp) throws IOException
final JFreeChart chart = ChartFactory.createLineChart(null, // chart title
- null, // unused
- "%", // range axis label
- dsb.build(), // data
- PlotOrientation.VERTICAL, // orientation
- true, // include legend
- true, // tooltips
- false // urls
- );// JFreeChart chart = new MutationChart(this).createChart();
+ null, // unused
+ "%", // range axis label
+ dsb.build(), // data
+ PlotOrientation.VERTICAL, // orientation
+ true, // include legend
+ true, // tooltips
+ false // urls
+ );// JFreeChart chart = new MutationChart(this).createChart();
ChartUtil.generateGraph(req, rsp, chart, 500, 200);
}
}
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/PitLogger.java b/src/main/java/org/jenkinsci/plugins/pitmutation/PitLogger.java
new file mode 100644
index 0000000..5ea2380
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/PitLogger.java
@@ -0,0 +1,68 @@
+package org.jenkinsci.plugins.pitmutation;
+
+import hudson.FilePath;
+import hudson.model.TaskListener;
+import org.jenkinsci.plugins.pitmutation.targets.MutationStats;
+
+/**
+ * Logging class for various pit information.
+ *
+ * @author edward
+ * @author vasile.jureschi
+ */
+public class PitLogger {
+
+ PitLogger() {
+ }
+
+ public void logResults(TaskListener listener, PitBuildAction action) {
+ MutationStats mutationStats = action.getReport().getMutationStats();
+ PitBuildAction previousAction = action.getPreviousAction();
+ if (previousAction != null) {
+ float previousKillPercent = previousAction.getReport().getMutationStats().getKillPercent();
+ logPrevious(mutationStats.getKillPercent(), previousKillPercent, listener);
+ }
+ logCurrent(mutationStats, listener);
+ }
+
+ public void logMissingReportsIgnored(TaskListener listener) {
+ listener
+ .getLogger()
+ .println("Build successful as no reports generated and build set to ignore missing reports. "
+ + "If the reports should be present set 'ignoreMissingReports' to false and run the build again to fail the build.");
+ }
+
+ public void logBuildFailedNoReports(TaskListener listener) {
+ listener
+ .getLogger()
+ .println("Build failed as no reports generated and build set to not ignore missing reports. "
+ + "If no reports should be generated set 'ignoreMissingReports' to true and run the build again to pass the build.");
+ }
+
+ private void logCurrent(MutationStats currentStats, TaskListener listener) {
+ listener
+ .getLogger()
+ .printf("Kill ratio is %f \\u0025 (%d %d)%n",
+ currentStats.getKillPercent(),
+ currentStats.getKillCount(),
+ currentStats.getTotalMutations());
+ }
+
+
+ private void logPrevious(float currentKillPercent, float previousKillPercent, TaskListener listener) {
+ listener.getLogger().println("Previous kill ratio was " + previousKillPercent + "%");
+ listener.getLogger().println("This kill ration is " + currentKillPercent + "%");
+ }
+
+ public void logLookingForReports(TaskListener listener, FilePath workspace) {
+ listener.getLogger().println("Looking for PIT reports in " + workspace.getRemote());
+ }
+
+ public void logPublishReport(TaskListener listener, FilePath workspace) {
+ listener.getLogger().println("Publishing mutation report: " + workspace.getRemote());
+ }
+
+ public void log(TaskListener listener, String message) {
+ listener.getLogger().println(message);
+ }
+}
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/PitProjectAction.java b/src/main/java/org/jenkinsci/plugins/pitmutation/PitProjectAction.java
index 8db7ed7..75628b7 100644
--- a/src/main/java/org/jenkinsci/plugins/pitmutation/PitProjectAction.java
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/PitProjectAction.java
@@ -6,6 +6,8 @@
import java.io.IOException;
+import static hudson.model.Result.FAILURE;
+
/**
* @author Ed Kimber
*/
@@ -24,11 +26,9 @@ public PitProjectAction(AbstractProject, ?> project) {
*/
public PitBuildAction getLastResult() {
for (AbstractBuild, ?> b = project.getLastSuccessfulBuild(); b != null; b = b.getPreviousNotFailedBuild()) {
- if (b.getResult() == Result.FAILURE)
- continue;
+ if (b.getResult() == FAILURE) {continue;}
PitBuildAction r = b.getAction(PitBuildAction.class);
- if (r != null)
- return r;
+ if (r != null) {return r;}
}
return null;
}
@@ -44,11 +44,9 @@ public PitBuildAction getLastResult() {
*/
public Integer getLastResultBuild() {
for (AbstractBuild, ?> b = project.getLastSuccessfulBuild(); b != null; b = b.getPreviousNotFailedBuild()) {
- if (b.getResult() == Result.FAILURE)
- continue;
+ if (b.getResult() == FAILURE) {continue;}
PitBuildAction r = b.getAction(PitBuildAction.class);
- if (r != null)
- return b.getNumber();
+ if (r != null) {return b.getNumber();}
}
return null;
}
@@ -87,7 +85,6 @@ public void doIndex(StaplerRequest req, StaplerResponse rsp) throws IOException
}
public void doGraph(StaplerRequest req, StaplerResponse rsp) throws IOException {
- if (getLastResult() != null)
- getLastResult().doGraph(req, rsp);
+ if (getLastResult() != null) {getLastResult().doGraph(req, rsp);}
}
}
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/PitPublisher.java b/src/main/java/org/jenkinsci/plugins/pitmutation/PitPublisher.java
index 847d961..c6b40d6 100644
--- a/src/main/java/org/jenkinsci/plugins/pitmutation/PitPublisher.java
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/PitPublisher.java
@@ -1,65 +1,74 @@
package org.jenkinsci.plugins.pitmutation;
-import hudson.Extension;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import hudson.EnvVars;
import hudson.FilePath;
import hudson.Launcher;
import hudson.Util;
-import hudson.model.*;
-import hudson.remoting.VirtualChannel;
-import hudson.tasks.BuildStepDescriptor;
+import hudson.model.AbstractProject;
+import hudson.model.Action;
+import hudson.model.Run;
+import hudson.model.TaskListener;
import hudson.tasks.BuildStepMonitor;
-import hudson.tasks.Publisher;
import hudson.tasks.Recorder;
import jenkins.tasks.SimpleBuildStep;
-import net.sf.json.JSONObject;
-import org.apache.commons.lang.StringUtils;
-import org.jenkinsci.Symbol;
-import org.jenkinsci.plugins.pitmutation.targets.MutationStats;
-import org.jenkinsci.remoting.RoleChecker;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
-import org.kohsuke.stapler.StaplerRequest;
import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
+import java.util.Arrays;
+import java.util.Map;
+
+import static hudson.model.Result.FAILURE;
+import static hudson.model.Result.SUCCESS;
+import static hudson.tasks.BuildStepMonitor.STEP;
/**
* The type Pit publisher.
*
* @author edward
+ * @author vasile.jureschi
*/
public class PitPublisher extends Recorder implements SimpleBuildStep {
- public static final String SINGLE_MODULE_REPORT_FOLDER = "mutation-report-all";
- public static final String MULTI_MODULE_REPORT_FORMAT = "mutation-report-%s";
-
- private List buildConditions;
+ public static final String GLOB_MUTATIONS_XML = "**/target/pit-reports/**/mutations.xml";
private String mutationStatsFile;
private boolean killRatioMustImprove;
+ /**
+ * If true and no reports are generated, the missing reports will not cause a build failure. Uses for cases where
+ * there are test but nothing is mutated them and there are no reports generated.
+ */
+ private boolean ignoreMissingReports;
private float minimumKillRatio;
- private transient TaskListener listener;
- private Run, ?> build;
+
+ private FileProcessor fileProcessor;
+ private ResultDecider resultDecider;
+ private PitLogger pitLogger;
/**
- * Instantiates a new Pit publisher.
+ * Instantiates a new Pit publisher. Only used in tests.
*
* @param mutationStatsFile the mutation stats file
* @param minimumKillRatio the minimum kill ratio
* @param killRatioMustImprove the kill ratio must improve
+ * @param ignoreMissingReports do not fail the build if there are no reports
*/
- protected PitPublisher(String mutationStatsFile, float minimumKillRatio, boolean killRatioMustImprove) {
+ PitPublisher(String mutationStatsFile,
+ float minimumKillRatio,
+ boolean killRatioMustImprove,
+ boolean ignoreMissingReports,
+ FileProcessor fileProcessor,
+ ResultDecider resultDecider,
+ PitLogger pitLogger) {
this.mutationStatsFile = mutationStatsFile;
this.killRatioMustImprove = killRatioMustImprove;
this.minimumKillRatio = minimumKillRatio;
- this.buildConditions = new ArrayList<>();
- this.buildConditions.add(percentageThreshold(minimumKillRatio));
- if (killRatioMustImprove) {
- this.buildConditions.add(mustImprove());
- }
+ this.ignoreMissingReports = ignoreMissingReports;
+ this.fileProcessor = fileProcessor;
+ this.resultDecider = resultDecider;
+ this.pitLogger = pitLogger;
}
/**
@@ -68,132 +77,123 @@ protected PitPublisher(String mutationStatsFile, float minimumKillRatio, boolean
* {@link #mutationStatsFile} is set to {@code **{@literal /}target/pit-reports/**{@literal /}mutations.xml},
* {@link #minimumKillRatio} is set to {@code 0.0},
* {@link #killRatioMustImprove} is set to {@code false},
+ * {@link #ignoreMissingReports} is set to {@code false},
*/
@DataBoundConstructor
- public PitPublisher() {
- this("**/target/pit-reports/**/mutations.xml", 0, false);
+ public PitPublisher(String mutationStatsFile,
+ float minimumKillRatio,
+ boolean killRatioMustImprove,
+ boolean ignoreMissingReports) {
+ this(mutationStatsFile,
+ minimumKillRatio,
+ killRatioMustImprove,
+ ignoreMissingReports,
+ new FileProcessor(),
+ killRatioMustImprove ?
+ new ResultDecider(new PercentageThresholdCondition(minimumKillRatio)) :
+ new ResultDecider(new PercentageThresholdCondition(minimumKillRatio), new MustImproveCondition()),
+ new PitLogger());
}
@DataBoundSetter
- public void setMutationStatsFile(final String mutationStatsFile) {
- this.mutationStatsFile = mutationStatsFile;
- }
-
- @DataBoundSetter
- public void setMinimumKillRatio(final float minimumKillRatio) {
- this.minimumKillRatio = minimumKillRatio;
- }
-
- @DataBoundSetter
- public void setKillRatioMustImprove(final boolean killRatioMustImprove) {
- this.killRatioMustImprove = killRatioMustImprove;
+ public void setIgnoreMissingReports(final boolean ignoreMissingReports) {
+ this.ignoreMissingReports = ignoreMissingReports;
}
@Override
- public void perform(@Nonnull Run, ?> build, @Nonnull FilePath workspace, @Nonnull Launcher launcher, @Nonnull TaskListener listener) throws InterruptedException, IOException {
- this.listener = listener;
- this.build = build;
-
- Result result = build.getResult();
- if (build instanceof AbstractBuild, ?> && result != null && result.isBetterOrEqualTo(Result.UNSTABLE)) {
- AbstractBuild, ?> abstractBuild = (AbstractBuild, ?>) build;
- this.listener.getLogger().println("Looking for PIT reports in " + abstractBuild.getModuleRoot().getRemote());
+ public void perform(@Nonnull Run, ?> build,
+ @Nonnull FilePath workspace,
+ @NonNull EnvVars env,
+ @Nonnull Launcher launcher,
+ @Nonnull TaskListener listener) {
+ try {
+ process(build, workspace, env, launcher, listener);
+ } catch (IOException e) {
+ Util.displayIOException(e, listener);
+ build.setResult(FAILURE);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
- final FilePath[] moduleRoots = abstractBuild.getModuleRoots();
- final boolean multipleModuleRoots = moduleRoots != null && moduleRoots.length > 1;
- final FilePath moduleRoot = multipleModuleRoots ? abstractBuild.getWorkspace() : abstractBuild.getModuleRoot();
- if (moduleRoot == null) {
- this.listener.getLogger().println("Module root was returned as null");
- return;
- }
+ private void process(@Nonnull Run, ?> build,
+ @Nonnull FilePath workspace,
+ @NonNull EnvVars env,
+ @Nonnull Launcher launcher,
+ @Nonnull TaskListener listener) throws InterruptedException, IOException {
+ FilePath[] reports = workspace.act(new ParseReportCallable(mutationStatsFile));
- ParseReportCallable fileCallable = new ParseReportCallable(mutationStatsFile);
- FilePath[] reports = moduleRoot.act(fileCallable);
- publishReports(reports, new FilePath(abstractBuild.getRootDir()), abstractBuild.getModuleRoot().getRemote());
+ if (noReportsAndMissingReportsIgnored(reports)) {
+ pitLogger.logMissingReportsIgnored(listener);
+ build.setResult(SUCCESS);
+ return;
+ } else if (noReportsAndMissingReportsNotIgnored(reports)) {
+ pitLogger.logBuildFailedNoReports(listener);
+ build.setResult(FAILURE);
+ return;
+ }
- //publish latest reports
- PitBuildAction action = new PitBuildAction(abstractBuild);
- abstractBuild.addAction(action);
- abstractBuild.setResult(decideBuildResult(action));
+ pitLogger.logLookingForReports(listener, workspace);
+ FilePath buildTarget = new FilePath(build.getRootDir());
+ if (reports.length == 1) {
+ fileProcessor.copySingleModuleReport(reports[0], buildTarget);
} else {
- this.listener.getLogger().println("Looking for PIT reports in " + workspace.getRemote());
-
- ParseReportCallable fileCallable = new ParseReportCallable(mutationStatsFile);
- FilePath[] reports = workspace.act(fileCallable);
- FilePath buildTarget = new FilePath(build.getRootDir());
- if (reports.length == 1) {
- copyMutationReports(reports[0], buildTarget, SINGLE_MODULE_REPORT_FOLDER);
- } else {
- publishReports(reports, buildTarget, workspace.getRemote());
+ for (Map.Entry entry : fileProcessor
+ .getNames(reports, workspace.getRemote())
+ .entrySet()) {
+ FilePath filePath = entry.getKey();
+ String module = entry.getValue();
+ pitLogger.logPublishReport(listener, workspace);
+ fileProcessor.copyMultiModuleReport(filePath, buildTarget, module);
}
- PitBuildAction action = new PitBuildAction(build);
- build.addAction(action);
- build.setResult(decideBuildResult(action));
}
- }
- /**
- * {@inheritDoc}
- */
- @Override
- public Action getProjectAction(AbstractProject, ?> project) {
- return new PitProjectAction(project);
- }
- /**
- * Publish reports.
- *
- * @param reports the reports
- * @param buildTarget the build target
- * @param base the base path of the report location
- */
- void publishReports(FilePath[] reports, FilePath buildTarget, final String base) {
- for (int i = 0; i < reports.length; i++) {
- FilePath report = reports[i];
- listener.getLogger().println("Publishing mutation report: " + report.getRemote());
+ PitBuildAction action = new PitBuildAction(build);
+ build.addAction(action);
+ build.setResult(resultDecider.decideBuildResult(action));
- final String moduleName;
- if (StringUtils.isBlank(base)) {
- moduleName = String.valueOf(i == 0 ? null : i);
- } else {
- String[] partsFromRemoteWithoutBase = report.getRemote().replace(base, "").split("[/\\\\]");
- if (partsFromRemoteWithoutBase.length > 1) {
- moduleName = partsFromRemoteWithoutBase[1];
- } else {
- moduleName = String.valueOf(i == 0 ? null : i);
- }
+ pitLogger.logResults(listener, action);
+ }
+
+ private boolean noReportsAndMissingReportsIgnored(FilePath[] reports) {
+ if (reports == null && ignoreMissingReports) {
+ return true;
+ } else if (reports == null) {
+ return false;
+ }
+ return Arrays.stream(reports).anyMatch(report -> {
+ try {
+ return (!report.exists() || report.isDirectory()) && ignoreMissingReports;
+ } catch (IOException | InterruptedException e) {
+ e.printStackTrace();
+ return false;
}
- copyMutationReports(reports[i], buildTarget, String.format(MULTI_MODULE_REPORT_FORMAT, moduleName));
- }
+ });
}
- private void copyMutationReports(FilePath source, FilePath buildTarget, String mutationFilePath) {
- final FilePath targetPath = new FilePath(buildTarget, mutationFilePath);
- try {
- FilePath parent = Optional.ofNullable(source.getParent()).orElseThrow(() -> new IOException());
- parent.copyRecursiveTo(targetPath);
- } catch (IOException e) {
- Util.displayIOException(e, listener);
- e.printStackTrace(listener.fatalError("Unable to copy coverage from " + source + " to " + buildTarget));
- build.setResult(Result.FAILURE);
- } catch (InterruptedException e) {
- e.printStackTrace();
+ private boolean noReportsAndMissingReportsNotIgnored(FilePath[] reports) {
+ if (reports == null && !ignoreMissingReports) {
+ return false;
+ } else if (reports == null) {
+ return true;
}
+ return Arrays.stream(reports).anyMatch(report -> {
+ try {
+ return (!report.exists() || report.isDirectory()) && !ignoreMissingReports;
+ } catch (IOException | InterruptedException e) {
+ e.printStackTrace();
+ return false;
+ }
+ });
}
/**
- * Decide build result result.
- *
- * @param action the action
- * @return the worst result from all conditions
+ * {@inheritDoc}
*/
- public Result decideBuildResult(PitBuildAction action) {
- Result result = Result.SUCCESS;
- for (Condition condition : buildConditions) {
- Result conditionResult = condition.decideResult(action);
- result = conditionResult.isWorseThan(result) ? conditionResult : result;
- }
- return result;
+ @Override
+ public Action getProjectAction(AbstractProject, ?> project) {
+ return new PitProjectAction(project);
}
@@ -224,126 +224,18 @@ public String getMutationStatsFile() {
return mutationStatsFile;
}
- Condition percentageThreshold(final float percentage) {
- return new PercentageThresholdCondition(percentage);
- }
-
- class PercentageThresholdCondition implements Condition {
- private final float percentage;
-
- PercentageThresholdCondition(float percentage) {
- super();
- this.percentage = percentage;
- }
-
- @Override
- public Result decideResult(PitBuildAction action) {
- MutationStats stats = action.getReport().getMutationStats();
- dologging(stats);
- return stats.getKillPercent() >= percentage ? Result.SUCCESS : Result.FAILURE;
- }
-
- void dologging(MutationStats stats) {
- listener.getLogger().println("Kill ratio is " + stats.getKillPercent() + "% (" + stats.getKillCount()
- + " " + stats.getTotalMutations() + ")");
- }
- }
-
- class MustImproveCondition implements Condition {
- @Override
- public Result decideResult(final PitBuildAction action) {
- PitBuildAction previousAction = action.getPreviousAction();
- if (previousAction != null) {
- MutationStats previousStats = previousAction.getReport().getMutationStats();
- logInfo(action, previousStats);
- return action.getReport().getMutationStats().getKillPercent() >= previousStats.getKillPercent() ? Result.SUCCESS
- : Result.UNSTABLE;
- } else {
- return Result.SUCCESS;
- }
- }
-
- void logInfo(final PitBuildAction action, MutationStats stats) {
- listener.getLogger().println("Previous kill ratio was " + stats.getKillPercent() + "%");
- listener.getLogger()
- .println("This kill ration is " + action.getReport().getMutationStats().getKillPercent() + "%");
- }
- }
-
- Condition mustImprove() {
- return new MustImproveCondition();
+ /**
+ * Required by plugin config
+ *
+ * @return ignore missing reports flag
+ */
+ public boolean getIgnoreMissingReports() {
+ return ignoreMissingReports;
}
@Override
public BuildStepMonitor getRequiredMonitorService() {
- return BuildStepMonitor.BUILD;
- }
-
- /**
- * The type Descriptor.
- */
- @Extension
- @Symbol("pitmutation")
- public static class DescriptorImpl extends BuildStepDescriptor {
-
- /**
- * Instantiates a new Descriptor.
- */
- public DescriptorImpl() {
- super(PitPublisher.class);
- }
-
- @Override
- public String getDisplayName() {
- return Messages.PitPublisher_DisplayName();
- }
-
- @Override
- public boolean isApplicable(Class extends AbstractProject> aClass) {
- return true;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
- req.bindParameters(this, "pitmutation");
- save();
- return super.configure(req, formData);
- }
+ return STEP;
}
- /**
- * The type Parse report callable.
- */
- public static class ParseReportCallable implements FilePath.FileCallable {
-
- private static final long serialVersionUID = 1L;
-
- private final String reportFilePath;
-
- /**
- * Instantiates a new Parse report callable.
- *
- * @param reportFilePath the report file path
- */
- public ParseReportCallable(String reportFilePath) {
- this.reportFilePath = reportFilePath;
- }
-
- @Override
- public FilePath[] invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
- FilePath[] r = new FilePath(f).list(reportFilePath);
- if (r.length < 1) {
- throw new IOException("No reports found at location:" + reportFilePath);
- }
- return r;
- }
-
- @Override
- public void checkRoles(RoleChecker roleChecker) throws SecurityException {
-
- }
- }
}
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/ResultDecider.java b/src/main/java/org/jenkinsci/plugins/pitmutation/ResultDecider.java
new file mode 100644
index 0000000..6965555
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/ResultDecider.java
@@ -0,0 +1,43 @@
+package org.jenkinsci.plugins.pitmutation;
+
+import hudson.model.Result;
+
+import java.util.Collection;
+import java.util.List;
+
+import static hudson.model.Result.SUCCESS;
+
+/**
+ * Decides if a build is failed or not based on conditions.
+ *
+ * @author edward
+ * @author vasile.jureschi
+ */
+public class ResultDecider {
+
+ private final Collection buildConditions;
+
+ protected ResultDecider(PercentageThresholdCondition thresholdCondition) {
+ buildConditions = List.of(thresholdCondition);
+ }
+
+ protected ResultDecider(PercentageThresholdCondition thresholdCondition,
+ MustImproveCondition mustImproveCondition) {
+ buildConditions = List.of(thresholdCondition, mustImproveCondition);
+ }
+
+ /**
+ * Decide build result result.
+ *
+ * @param action the action
+ * @return the worst result from all conditions
+ */
+ public Result decideBuildResult(PitBuildAction action) {
+ Result result = SUCCESS;
+ for (Condition condition : buildConditions) {
+ Result conditionResult = condition.decideResult(action);
+ result = conditionResult.isWorseThan(result) ? conditionResult : result;
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/portlets/CoverageRange.java b/src/main/java/org/jenkinsci/plugins/pitmutation/portlets/CoverageRange.java
index 9f2ced4..f0d455e 100644
--- a/src/main/java/org/jenkinsci/plugins/pitmutation/portlets/CoverageRange.java
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/portlets/CoverageRange.java
@@ -63,14 +63,14 @@ public static Color fillColorOf(final double amount) {
return ABYSMAL.fillColor;
}
- private static Color blendedColor(final Color fillColor0, final Color fillColor1, final double t0, final double t1) {
+ private static Color blendedColor(final Color fillColor0,
+ final Color fillColor1,
+ final double t0,
+ final double t1) {
final double total = t0 + t1;
- final int r = (int) ((fillColor0.getRed() * t1 + fillColor1.getRed()
- * t0) / total);
- final int g = (int) ((fillColor0.getGreen() * t1 + fillColor1
- .getGreen() * t0) / total);
- final int b = (int) ((fillColor0.getBlue() * t1 + fillColor1.getBlue()
- * t0) / total);
+ final int r = (int) ((fillColor0.getRed() * t1 + fillColor1.getRed() * t0) / total);
+ final int g = (int) ((fillColor0.getGreen() * t1 + fillColor1.getGreen() * t0) / total);
+ final int b = (int) ((fillColor0.getBlue() * t1 + fillColor1.getBlue() * t0) / total);
return new Color(r, g, b);
}
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/portlets/PitColumn.java b/src/main/java/org/jenkinsci/plugins/pitmutation/portlets/PitColumn.java
index 1702a62..61a153a 100644
--- a/src/main/java/org/jenkinsci/plugins/pitmutation/portlets/PitColumn.java
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/portlets/PitColumn.java
@@ -52,11 +52,10 @@ private Double getLinePercent(final Run, ?> lastSuccessfulBuild) {
final double doubleValue = percentageFloat.doubleValue();
final int decimalPlaces = 2;
- BigDecimal bigDecimal = new BigDecimal(doubleValue);
+ BigDecimal bigDecimal = BigDecimal.valueOf(doubleValue);
// setScale is immutable
- bigDecimal = bigDecimal.setScale(decimalPlaces,
- RoundingMode.HALF_UP);
+ bigDecimal = bigDecimal.setScale(decimalPlaces, RoundingMode.HALF_UP);
return bigDecimal.doubleValue();
}
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/targets/BaseResultFactory.java b/src/main/java/org/jenkinsci/plugins/pitmutation/targets/BaseResultFactory.java
index f08ea2d..441038f 100644
--- a/src/main/java/org/jenkinsci/plugins/pitmutation/targets/BaseResultFactory.java
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/targets/BaseResultFactory.java
@@ -12,7 +12,6 @@ private BaseResultFactory() {
public static MutationResult getBaseMutationResultFrom(PitBuildAction action) {
Map reports = action.getReports();
- return reports.size() == 1 ? new SingleModuleResult(action) :
- new ProjectMutations(action);
+ return reports.size() == 1 ? new SingleModuleResult(action) : new ProjectMutations(action);
}
}
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/targets/ModuleChildMapBuilder.java b/src/main/java/org/jenkinsci/plugins/pitmutation/targets/ModuleChildMapBuilder.java
index 7080516..f5c2f2e 100644
--- a/src/main/java/org/jenkinsci/plugins/pitmutation/targets/ModuleChildMapBuilder.java
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/targets/ModuleChildMapBuilder.java
@@ -33,8 +33,6 @@ public Map build() {
private String getClassName(Mutation mutation) {
String mutatedClassName = mutation.getMutatedClass();
int firstDollar = mutatedClassName.indexOf('$');
- return firstDollar >= 0
- ? mutatedClassName.substring(0, firstDollar)
- : mutatedClassName;
+ return firstDollar >= 0 ? mutatedClassName.substring(0, firstDollar) : mutatedClassName;
}
}
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/targets/ModuleResult.java b/src/main/java/org/jenkinsci/plugins/pitmutation/targets/ModuleResult.java
index 8326de0..eb07ae7 100644
--- a/src/main/java/org/jenkinsci/plugins/pitmutation/targets/ModuleResult.java
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/targets/ModuleResult.java
@@ -1,15 +1,14 @@
package org.jenkinsci.plugins.pitmutation.targets;
-import static org.jenkinsci.plugins.pitmutation.PitPublisher.MULTI_MODULE_REPORT_FORMAT;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.jenkinsci.plugins.pitmutation.MutationReport;
+import javax.annotation.Nonnull;
import java.util.Map;
import java.util.Objects;
-import javax.annotation.Nonnull;
-import org.jenkinsci.plugins.pitmutation.MutationReport;
-
-import lombok.Getter;
-import lombok.extern.slf4j.Slf4j;
+import static org.jenkinsci.plugins.pitmutation.FileProcessor.MULTI_MODULE_REPORT_FORMAT;
/**
* @author edward
@@ -17,9 +16,9 @@
@Slf4j
public class ModuleResult extends MutationResult {
- private MutationReport report;
+ private final MutationReport report;
@Getter
- private String name;
+ private final String name;
public ModuleResult(String name, MutationResult parent, MutationReport report) {
super(name, parent);
@@ -54,13 +53,10 @@ public int compareTo(@Nonnull ModuleResult other) {
@Override
public boolean equals(Object o) {
- if (this == o)
- return true;
- if (o == null || getClass() != o.getClass())
- return false;
+ if (this == o) {return true;}
+ if (o == null || getClass() != o.getClass()) {return false;}
ModuleResult that = (ModuleResult) o;
- return Objects.equals(report, that.report) &&
- Objects.equals(name, that.name);
+ return Objects.equals(report, that.report) && Objects.equals(name, that.name);
}
@Override
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/targets/MutatedClass.java b/src/main/java/org/jenkinsci/plugins/pitmutation/targets/MutatedClass.java
index a942a50..dcf2a1c 100644
--- a/src/main/java/org/jenkinsci/plugins/pitmutation/targets/MutatedClass.java
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/targets/MutatedClass.java
@@ -43,7 +43,8 @@ public MutatedClass(String name, MutationResult parent, Collection mut
}
private Map createMutatedLines(Collection mutations) {
- return mutations.stream()
+ return mutations
+ .stream()
.collect(groupingBy(Mutation::getLineNumber))
.values()
.stream()
@@ -58,17 +59,20 @@ public boolean isSourceLevel() {
/**
* Gets the contents of the coverage report for the file, but removes the header and the stylesheet as this needs to be handled separately in jelly.
+ *
* @return The source of the coverage report to show in the UI
*/
@Override
public String getSourceFileContent() {
String fullContents = getFileContents(package_ + File.separator + fileName);
- return fullContents.contains(END_HEADER_TAG) ? fullContents.substring(fullContents.indexOf(END_HEADER_TAG) + 5) :
- fullContents;
+ return fullContents.contains(END_HEADER_TAG) ?
+ fullContents.substring(fullContents.indexOf(END_HEADER_TAG) + 5) :
+ fullContents;
}
/**
* Gets the contents of the style sheet for the coverage report.
+ *
* @return The source of the coverage report to show in the UI.
*/
@Override
@@ -78,12 +82,10 @@ public String getStyleSheetContent() {
private String getFileContents(String path) {
String filePath =
- getOwner().getRootDir() + File.separator + getMutationReportDirectory() +
- File.separator + path;
+ getOwner().getRootDir() + File.separator + getMutationReportDirectory() + File.separator + path;
try {
return new TextFile(new File(filePath)).read();
- }
- catch (IOException exception) {
+ } catch (IOException exception) {
return "Could not read file: " + filePath + "\n";
}
}
@@ -110,16 +112,15 @@ public int compareTo(@Nonnull MutatedClass other) {
@Override
public boolean equals(Object o) {
- if (this == o)
- return true;
- if (o == null || getClass() != o.getClass())
- return false;
+ if (this == o) {return true;}
+ if (o == null || getClass() != o.getClass()) {return false;}
MutatedClass that = (MutatedClass) o;
- return Objects.equals(name, that.name) &&
- Objects.equals(package_, that.package_) &&
- Objects.equals(fileName, that.fileName) &&
- Objects.equals(mutations, that.mutations) &&
- Objects.equals(mutatedLines, that.mutatedLines);
+ return Objects.equals(name, that.name)
+ && Objects.equals(package_, that.package_)
+ && Objects.equals(fileName,
+ that.fileName)
+ && Objects.equals(mutations, that.mutations)
+ && Objects.equals(mutatedLines, that.mutatedLines);
}
@Override
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/targets/MutatedLine.java b/src/main/java/org/jenkinsci/plugins/pitmutation/targets/MutatedLine.java
index 8de043d..eaf7821 100644
--- a/src/main/java/org/jenkinsci/plugins/pitmutation/targets/MutatedLine.java
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/targets/MutatedLine.java
@@ -68,13 +68,10 @@ public int compareTo(@Nonnull MutatedLine other) {
@Override
public boolean equals(Object o) {
- if (this == o)
- return true;
- if (o == null || getClass() != o.getClass())
- return false;
+ if (this == o) {return true;}
+ if (o == null || getClass() != o.getClass()) {return false;}
MutatedLine that = (MutatedLine) o;
- return lineNumber == that.lineNumber &&
- Objects.equals(mutations, that.mutations);
+ return lineNumber == that.lineNumber && Objects.equals(mutations, that.mutations);
}
@Override
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/targets/MutatedPackage.java b/src/main/java/org/jenkinsci/plugins/pitmutation/targets/MutatedPackage.java
index 6ed76d5..e9e476c 100644
--- a/src/main/java/org/jenkinsci/plugins/pitmutation/targets/MutatedPackage.java
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/targets/MutatedPackage.java
@@ -31,7 +31,8 @@ public String getDisplayName() {
@Override
public MutationStats getMutationStats() {
- return new MutationStatsImpl(getName(), classMutations.values().stream().flatMap(List::stream).collect(toList()));
+ return new MutationStatsImpl(getName(),
+ classMutations.values().stream().flatMap(List::stream).collect(toList()));
}
@Override
@@ -53,10 +54,8 @@ public int compareTo(@Nonnull MutatedPackage other) {
@Override
public boolean equals(Object o) {
- if (this == o)
- return true;
- if (o == null || getClass() != o.getClass())
- return false;
+ if (this == o) {return true;}
+ if (o == null || getClass() != o.getClass()) {return false;}
MutatedPackage that = (MutatedPackage) o;
return Objects.equals(classMutations, that.classMutations);
}
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/targets/MutationResult.java b/src/main/java/org/jenkinsci/plugins/pitmutation/targets/MutationResult.java
index 30184b2..46254b4 100644
--- a/src/main/java/org/jenkinsci/plugins/pitmutation/targets/MutationResult.java
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/targets/MutationResult.java
@@ -81,10 +81,7 @@ public boolean isCoverageAltered() {
}
public Collection extends MutationResult> getChildren() {
- return getChildMap().values().stream()
- .sorted()
- .sorted(Collections.reverseOrder())
- .collect(Collectors.toList());
+ return getChildMap().values().stream().sorted().sorted(Collections.reverseOrder()).collect(Collectors.toList());
}
public MutationStats getStatsDelta() {
@@ -129,9 +126,7 @@ static String urlTransform(String token) {
StringBuilder buf = new StringBuilder(token.length());
for (int i = 0; i < token.length(); i++) {
final char c = token.charAt(i);
- if (('0' <= c && '9' >= c)
- || ('A' <= c && 'Z' >= c)
- || ('a' <= c && 'z' >= c)) {
+ if (('0' <= c && '9' >= c) || ('A' <= c && 'Z' >= c) || ('a' <= c && 'z' >= c)) {
buf.append(c);
} else {
buf.append('_');
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/targets/MutationStats.java b/src/main/java/org/jenkinsci/plugins/pitmutation/targets/MutationStats.java
index f068775..c727797 100644
--- a/src/main/java/org/jenkinsci/plugins/pitmutation/targets/MutationStats.java
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/targets/MutationStats.java
@@ -28,7 +28,9 @@ public String getKillPercentFillColor() {
return getCoverageColors().getFillHexString();
}
- public String getKillPercentTextColor() { return getCoverageColors().getLineHexString();}
+ public String getKillPercentTextColor() {
+ return getCoverageColors().getLineHexString();
+ }
private CoverageRange getCoverageColors() {
return CoverageRange.valueOf(getKillPercent());
@@ -36,7 +38,7 @@ private CoverageRange getCoverageColors() {
private float round(float ratio) {
//TODO NaN mutation test
- if (Float.isNaN(ratio) || Float.isInfinite(ratio)) return ratio;
+ if (Float.isNaN(ratio) || Float.isInfinite(ratio)) {return ratio;}
BigDecimal bd = new BigDecimal(ratio);
BigDecimal rounded = bd.setScale(3, RoundingMode.HALF_UP);
return rounded.floatValue();
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/targets/ProjectMutations.java b/src/main/java/org/jenkinsci/plugins/pitmutation/targets/ProjectMutations.java
index dc553f5..eec9a02 100644
--- a/src/main/java/org/jenkinsci/plugins/pitmutation/targets/ProjectMutations.java
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/targets/ProjectMutations.java
@@ -70,10 +70,8 @@ public int compareTo(@Nonnull ProjectMutations other) {
@Override
public boolean equals(Object o) {
- if (this == o)
- return true;
- if (o == null || getClass() != o.getClass())
- return false;
+ if (this == o) {return true;}
+ if (o == null || getClass() != o.getClass()) {return false;}
ProjectMutations that = (ProjectMutations) o;
return Objects.equals(action, that.action);
}
diff --git a/src/main/java/org/jenkinsci/plugins/pitmutation/targets/SingleModuleResult.java b/src/main/java/org/jenkinsci/plugins/pitmutation/targets/SingleModuleResult.java
index 10992e4..eda0c4f 100644
--- a/src/main/java/org/jenkinsci/plugins/pitmutation/targets/SingleModuleResult.java
+++ b/src/main/java/org/jenkinsci/plugins/pitmutation/targets/SingleModuleResult.java
@@ -1,16 +1,16 @@
package org.jenkinsci.plugins.pitmutation.targets;
+import hudson.model.Run;
+import org.jenkinsci.plugins.pitmutation.MutationReport;
+import org.jenkinsci.plugins.pitmutation.PitBuildAction;
+
+import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
-import javax.annotation.Nonnull;
-import org.jenkinsci.plugins.pitmutation.MutationReport;
-import org.jenkinsci.plugins.pitmutation.PitBuildAction;
-import org.jenkinsci.plugins.pitmutation.PitPublisher;
-
-import hudson.model.Run;
+import static org.jenkinsci.plugins.pitmutation.FileProcessor.SINGLE_MODULE_REPORT_FOLDER;
public class SingleModuleResult extends MutationResult {
@@ -57,7 +57,7 @@ public String getDisplayName() {
@Override
protected String getMutationReportDirectory() {
- return PitPublisher.SINGLE_MODULE_REPORT_FOLDER;
+ return SINGLE_MODULE_REPORT_FOLDER;
}
@Override
@@ -77,10 +77,8 @@ public int compareTo(@Nonnull SingleModuleResult other) {
@Override
public boolean equals(Object o) {
- if (this == o)
- return true;
- if (o == null || getClass() != o.getClass())
- return false;
+ if (this == o) {return true;}
+ if (o == null || getClass() != o.getClass()) {return false;}
SingleModuleResult that = (SingleModuleResult) o;
return Objects.equals(action, that.action);
}
diff --git a/src/main/resources/org/jenkinsci/plugins/pitmutation/PitPublisher/config.jelly b/src/main/resources/org/jenkinsci/plugins/pitmutation/PitPublisher/config.jelly
index c3d21e8..d243ebf 100644
--- a/src/main/resources/org/jenkinsci/plugins/pitmutation/PitPublisher/config.jelly
+++ b/src/main/resources/org/jenkinsci/plugins/pitmutation/PitPublisher/config.jelly
@@ -23,4 +23,9 @@
description="The build will be marked unstable if the kill ratio is lower than the last build.">
+
+
+
diff --git a/src/test/java/org/jenkinsci/plugins/pitmutation/FileProcessorTest.java b/src/test/java/org/jenkinsci/plugins/pitmutation/FileProcessorTest.java
new file mode 100644
index 0000000..dfb1ecc
--- /dev/null
+++ b/src/test/java/org/jenkinsci/plugins/pitmutation/FileProcessorTest.java
@@ -0,0 +1,47 @@
+package org.jenkinsci.plugins.pitmutation;
+
+import hudson.FilePath;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.io.IOException;
+
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class FileProcessorTest {
+
+ @Mock
+ private FilePath source;
+
+ @Mock
+ private FilePath buildTarget;
+
+ private FileProcessor fileProcessor;
+
+ @BeforeEach
+ public void setUp() {
+ MockitoAnnotations.openMocks(this);
+ fileProcessor = new FileProcessor();
+ when(source.getParent()).thenReturn(source);
+ }
+
+ @Test
+ void copySingleModuleReport() throws Exception {
+ fileProcessor.copySingleModuleReport(source, buildTarget);
+ verify(source, times(1)).getParent();
+ }
+
+ @Test
+ void copyMultiModuleReport() throws Exception {
+ String module = "module1";
+ fileProcessor.copyMultiModuleReport(source, buildTarget, module);
+ verify(source, times(1)).getParent();
+ }
+
+ // TODO: Add more tests for other methods in FileProcessor
+}
diff --git a/src/test/java/org/jenkinsci/plugins/pitmutation/MustImproveConditionTest.java b/src/test/java/org/jenkinsci/plugins/pitmutation/MustImproveConditionTest.java
new file mode 100644
index 0000000..268d757
--- /dev/null
+++ b/src/test/java/org/jenkinsci/plugins/pitmutation/MustImproveConditionTest.java
@@ -0,0 +1,64 @@
+package org.jenkinsci.plugins.pitmutation;
+
+import hudson.model.Result;
+import org.jenkinsci.plugins.pitmutation.targets.MutationResult;
+import org.jenkinsci.plugins.pitmutation.targets.MutationStats;
+import org.junit.jupiter.api.DynamicTest;
+import org.junit.jupiter.api.TestFactory;
+
+import java.util.stream.Stream;
+
+import static hudson.model.Result.SUCCESS;
+import static hudson.model.Result.UNSTABLE;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.DynamicTest.dynamicTest;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class MustImproveConditionTest {
+
+ @TestFactory
+ Stream testFactory() {
+ return Stream
+ .of(new TestCase(null, 90f, SUCCESS, "noPreviousAction"),
+ new TestCase(85f, 90f, SUCCESS, "currentKillPercentGreater"),
+ new TestCase(85f, 85f, SUCCESS, "currentKillPercentEqual"),
+ new TestCase(85f, 80f, UNSTABLE, "currentKillPercentLess"))
+ .map(testCase -> dynamicTest(testCase.name, () -> {
+ PitBuildAction action = mock(PitBuildAction.class);
+ PitBuildAction previousAction = mock(PitBuildAction.class);
+ MutationResult currentReport = mock(MutationResult.class);
+ MutationResult previousReport = mock(MutationResult.class);
+ MutationStats currentStats = mock(MutationStats.class);
+ MutationStats previousStats = mock(MutationStats.class);
+
+ when(action.getReport()).thenReturn(currentReport);
+ when(currentReport.getMutationStats()).thenReturn(currentStats);
+ when(currentStats.getKillPercent()).thenReturn(testCase.currentKillPercent);
+
+ if (testCase.previousKillPercent != null) {
+ when(action.getPreviousAction()).thenReturn(previousAction);
+ when(previousAction.getReport()).thenReturn(previousReport);
+ when(previousReport.getMutationStats()).thenReturn(previousStats);
+ when(previousStats.getKillPercent()).thenReturn(testCase.previousKillPercent);
+ }
+
+ MustImproveCondition condition = new MustImproveCondition();
+ assertEquals(testCase.expectedResult, condition.decideResult(action));
+ }));
+ }
+
+ private static class TestCase {
+ Float previousKillPercent;
+ float currentKillPercent;
+ Result expectedResult;
+ String name;
+
+ TestCase(Float previousKillPercent, float currentKillPercent, Result expectedResult, String name) {
+ this.previousKillPercent = previousKillPercent;
+ this.currentKillPercent = currentKillPercent;
+ this.expectedResult = expectedResult;
+ this.name = name;
+ }
+ }
+}
diff --git a/src/test/java/org/jenkinsci/plugins/pitmutation/MutationReportTest.java b/src/test/java/org/jenkinsci/plugins/pitmutation/MutationReportTest.java
index 21129fb..b7c1624 100644
--- a/src/test/java/org/jenkinsci/plugins/pitmutation/MutationReportTest.java
+++ b/src/test/java/org/jenkinsci/plugins/pitmutation/MutationReportTest.java
@@ -1,7 +1,7 @@
package org.jenkinsci.plugins.pitmutation;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.xml.sax.SAXException;
import java.io.ByteArrayInputStream;
@@ -17,73 +17,96 @@
/**
* @author edward
*/
-public class MutationReportTest {
-
- private static final String MUTATIONS =
- ""
- + "\n"
- + "SafeMultipartFile.java\n"
- + "com.mediagraft.podsplice.controllers.massupload.SafeMultipartFile\n"
- + "getSize\n"
- + "54\n"
- + "org.pitest.mutationtest.engine.gregor.mutators.ReturnValsMutator\n"
- + "5\n"
- + "\n"
- + ""
- + ""
- + "SafeMultipartFile.java"
- + "com.mediagraft.podsplice.controllers.massupload.SafeMultipartFile"
- + "getSize"
- + "57"
- + "org.pitest.mutationtest.engine.gregor.mutators.ReturnValsMutator"
- + "6"
- + "3"
- + ""
- + ""
- + "";
+class MutationReportTest {
+
+ private static final String MUTATIONS_OLD = ""
+ + "\n"
+ + "SafeMultipartFile.java\n"
+ + "com.mediagraft.podsplice.controllers.massupload.SafeMultipartFile\n"
+ + "getSize\n"
+ + "54\n"
+ + "org.pitest.mutationtest.engine.gregor.mutators.ReturnValsMutator\n"
+ + "5\n"
+ + "\n"
+ + ""
+ + ""
+ + "SafeMultipartFile.java"
+ + "com.mediagraft.podsplice.controllers.massupload.SafeMultipartFile"
+ + "getSize"
+ + "57"
+ + "org.pitest.mutationtest.engine.gregor.mutators.ReturnValsMutator"
+ + "6"
+ + "3"
+ + ""
+ + ""
+ + "";
+
+ private static final String MUTATIONS = ""
+ + "\n"
+ + "SafeMultipartFile.java\n"
+ + "com.mediagraft.podsplice.controllers.massupload.SafeMultipartFile\n"
+ + "getSize\n"
+ + "54\n"
+ + "org.pitest.mutationtest.engine.gregor.mutators.ReturnValsMutator\n"
+ + "5\n"
+ + "\n"
+ + ""
+ + ""
+ + "SafeMultipartFile.java"
+ + "com.mediagraft.podsplice.controllers.massupload.SafeMultipartFile"
+ + "getSize"
+ + "57"
+ + "org.pitest.mutationtest.engine.gregor.mutators.ReturnValsMutator"
+ + "6"
+ + "3"
+ + ""
+ + ""
+ + "";
private InputStream mutationsXml;
- @Before
+ @BeforeEach
public void setUp() {
mutationsXml = getClass().getResourceAsStream("testmutations-00.xml");
}
@Test
- public void packageNameFinder() {
+ void packageNameFinder() {
assertThat(MutationReport.packageNameFromClass("xxx.yyy.zzz.Foo"), is("xxx.yyy.zzz"));
assertThat(MutationReport.packageNameFromClass("Foo"), is(""));
}
@Test
- public void countsKills() throws IOException, SAXException {
- MutationReport report = MutationReport.create(mutationsXml);
+ void countsKills() throws IOException, SAXException {
+ MutationReport report = new MutationReport(mutationsXml);
assertThat(report.getMutationStats().getKillCount(), is(5));
assertThat(report.getMutationStats().getTotalMutations(), is(16));
}
@Test
- public void sortsMutationsByClassName() throws IOException, SAXException {
- MutationReport report = MutationReport.create(mutationsXml);
- Collection mutations = report.getMutationsForClassName("org.jenkinsci.plugins.pitmutation.MutationReport");
+ void sortsMutationsByClassName() throws IOException, SAXException {
+ MutationReport report = new MutationReport(mutationsXml);
+ Collection mutations =
+ report.getMutationsForClassName("org.jenkinsci.plugins.pitmutation.MutationReport");
assertThat(mutations.size(), is(5));
}
@Test
- public void indexesMutationsByPackage() throws IOException, SAXException {
- MutationReport report = MutationReport.create(mutationsXml);
+ void indexesMutationsByPackage() throws IOException, SAXException {
+ MutationReport report = new MutationReport(mutationsXml);
assertThat(report.getMutationsForPackage("org.jenkinsci.plugins.pitmutation"), hasSize(16));
assertThat(report.getMutationsForPackage(""), hasSize(0));
}
@Test
- public void canDigestAMutation() throws IOException, SAXException {
- MutationReport report = MutationReport.create(new ByteArrayInputStream(MUTATIONS.getBytes("UTF-8")));
+ void canDigestAMutation() throws IOException, SAXException {
+ MutationReport report = new MutationReport(new ByteArrayInputStream(MUTATIONS.getBytes("UTF-8")));
assertThat(report.getMutationStats().getTotalMutations(), is(2));
- Iterator mutations =
- report.getMutationsForClassName("com.mediagraft.podsplice.controllers.massupload.SafeMultipartFile").iterator();
+ Iterator mutations = report
+ .getMutationsForClassName("com.mediagraft.podsplice.controllers.massupload.SafeMultipartFile")
+ .iterator();
Mutation m1 = mutations.next();
Mutation m2 = mutations.next();
@@ -97,6 +120,29 @@ public void canDigestAMutation() throws IOException, SAXException {
}
}
+ @Test
+ void canDigestAMutationOlderMutations() throws IOException, SAXException {
+ MutationReport report = new MutationReport(new ByteArrayInputStream(MUTATIONS_OLD.getBytes("UTF-8")));
+
+ assertThat(report.getMutationStats().getTotalMutations(), is(2));
+
+ Iterator mutations = report
+ .getMutationsForClassName("com.mediagraft.podsplice.controllers.massupload.SafeMultipartFile")
+ .iterator();
+
+ Mutation m1 = mutations.next();
+ Mutation m2 = mutations.next();
+
+ if (m1.getStatus().equals("KILLED")) {
+ verifyKilled(m1);
+ verifyNoCoverage(m2);
+ } else {
+ verifyKilled(m2);
+ verifyNoCoverage(m1);
+ }
+ }
+
+
private void verifyNoCoverage(Mutation m) {
assertThat(m.getLineNumber(), is(54));
assertThat(m.isDetected(), is(true));
diff --git a/src/test/java/org/jenkinsci/plugins/pitmutation/MutationReportWithDescriptionTest.java b/src/test/java/org/jenkinsci/plugins/pitmutation/MutationReportWithDescriptionTest.java
index 7e00a2b..991256a 100644
--- a/src/test/java/org/jenkinsci/plugins/pitmutation/MutationReportWithDescriptionTest.java
+++ b/src/test/java/org/jenkinsci/plugins/pitmutation/MutationReportWithDescriptionTest.java
@@ -1,53 +1,53 @@
package org.jenkinsci.plugins.pitmutation;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.hasSize;
-import static org.hamcrest.Matchers.notNullValue;
-import static org.hamcrest.core.Is.is;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.xml.sax.SAXException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
-import org.junit.Before;
-import org.junit.Test;
-import org.xml.sax.SAXException;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.core.Is.is;
/**
* @author edward
*/
-public class MutationReportWithDescriptionTest {
+class MutationReportWithDescriptionTest {
private InputStream mutationsXml;
- @Before
+ @BeforeEach
public void setUp() {
mutationsXml = getClass().getResourceAsStream("testmutations-02.xml");
}
@Test
- public void packageNameFinder() {
+ void packageNameFinder() {
assertThat(MutationReport.packageNameFromClass("xxx.yyy.zzz.Foo"), is("xxx.yyy.zzz"));
assertThat(MutationReport.packageNameFromClass("Foo"), is(""));
}
@Test
- public void countsKills() throws IOException, SAXException {
- MutationReport report = MutationReport.create(mutationsXml);
+ void countsKills() throws IOException, SAXException {
+ MutationReport report = new MutationReport(mutationsXml);
assertThat(report.getMutationStats().getKillCount(), is(3));
assertThat(report.getMutationStats().getTotalMutations(), is(4));
}
@Test
- public void sortsMutationsByClassName() throws IOException, SAXException {
- MutationReport report = MutationReport.create(mutationsXml);
+ void sortsMutationsByClassName() throws IOException, SAXException {
+ MutationReport report = new MutationReport(mutationsXml);
Collection mutations = report.getMutationsForClassName("es.rodri.controllers.CompositorController");
assertThat(mutations.size(), is(4));
}
@Test
- public void indexesMutationsByPackage() throws IOException, SAXException {
- MutationReport report = MutationReport.create(mutationsXml);
+ void indexesMutationsByPackage() throws IOException, SAXException {
+ MutationReport report = new MutationReport(mutationsXml);
assertThat(report.getMutationsForPackage("es.rodri.controllers"), hasSize(4));
assertThat(report.getMutationsForPackage(""), notNullValue());
assertThat(report.getMutationsForPackage(""), hasSize(0));
diff --git a/src/test/java/org/jenkinsci/plugins/pitmutation/PercentageThresholdConditionTest.java b/src/test/java/org/jenkinsci/plugins/pitmutation/PercentageThresholdConditionTest.java
new file mode 100644
index 0000000..7627004
--- /dev/null
+++ b/src/test/java/org/jenkinsci/plugins/pitmutation/PercentageThresholdConditionTest.java
@@ -0,0 +1,52 @@
+package org.jenkinsci.plugins.pitmutation;
+
+import hudson.model.Result;
+import org.jenkinsci.plugins.pitmutation.targets.MutationResult;
+import org.jenkinsci.plugins.pitmutation.targets.MutationStats;
+import org.junit.jupiter.api.DynamicTest;
+import org.junit.jupiter.api.TestFactory;
+
+import java.util.stream.Stream;
+
+import static hudson.model.Result.FAILURE;
+import static hudson.model.Result.SUCCESS;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.DynamicTest.dynamicTest;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class PercentageThresholdConditionTest {
+
+ @TestFactory
+ Stream testFactory() {
+ return Stream
+ .of(new TestCase(80f, 85f, SUCCESS, "killPercentGreaterThanThreshold"),
+ new TestCase(85f, 85f, SUCCESS, "killPercentEqualToThreshold"),
+ new TestCase(90f, 85f, FAILURE, "killPercentLessThanThreshold"))
+ .map(testCase -> dynamicTest(testCase.name, () -> {
+ PitBuildAction action = mock(PitBuildAction.class);
+ MutationResult report = mock(MutationResult.class);
+ MutationStats stats = mock(MutationStats.class);
+ when(action.getReport()).thenReturn(report);
+ when(report.getMutationStats()).thenReturn(stats);
+ when(stats.getKillPercent()).thenReturn(testCase.killPercent);
+
+ PercentageThresholdCondition condition = new PercentageThresholdCondition(testCase.threshold);
+ assertEquals(testCase.expectedResult, condition.decideResult(action));
+ }));
+ }
+
+ private static class TestCase {
+ float threshold;
+ float killPercent;
+ Result expectedResult;
+ String name;
+
+ TestCase(float threshold, float killPercent, Result expectedResult, String name) {
+ this.threshold = threshold;
+ this.killPercent = killPercent;
+ this.expectedResult = expectedResult;
+ this.name = name;
+ }
+ }
+}
diff --git a/src/test/java/org/jenkinsci/plugins/pitmutation/PitBuildActionTest.java b/src/test/java/org/jenkinsci/plugins/pitmutation/PitBuildActionTest.java
index dc855ee..be34ed7 100644
--- a/src/test/java/org/jenkinsci/plugins/pitmutation/PitBuildActionTest.java
+++ b/src/test/java/org/jenkinsci/plugins/pitmutation/PitBuildActionTest.java
@@ -11,7 +11,8 @@
import java.io.FilenameFilter;
import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import hudson.model.AbstractBuild;
import hudson.model.Result;
@@ -19,13 +20,13 @@
/**
* @author edward
*/
-public class PitBuildActionTest {
+class PitBuildActionTest {
private PitBuildAction action;
private AbstractBuild owner;
private AbstractBuild failedBuild;
private AbstractBuild successBuild;
- @Before
+ @BeforeEach
public void setUp() {
failedBuild = mock(AbstractBuild.class);
when(failedBuild.getResult()).thenReturn(Result.FAILURE);
@@ -43,18 +44,18 @@ public void setUp() {
}
@Test
- public void previousReturnsNullIfNoPreviousBuilds() {
+ void previousReturnsNullIfNoPreviousBuilds() {
assertThat(action.getPreviousAction(), nullValue());
}
@Test
- public void previousReturnsNullIfAllPreviousBuildsFailed() {
+ void previousReturnsNullIfAllPreviousBuildsFailed() {
when(owner.getPreviousBuild()).thenReturn(failedBuild);
assertThat(action.getPreviousAction(), nullValue());
}
@Test
- public void previousReturnsLastSuccessfulBuild() {
+ void previousReturnsLastSuccessfulBuild() {
PitBuildAction previousSucccessAction = mock(PitBuildAction.class);
when(owner.getPreviousBuild()).thenReturn(failedBuild);
when(failedBuild.getPreviousBuild()).thenReturn(successBuild);
diff --git a/src/test/java/org/jenkinsci/plugins/pitmutation/PitLoggerTest.java b/src/test/java/org/jenkinsci/plugins/pitmutation/PitLoggerTest.java
new file mode 100644
index 0000000..888c902
--- /dev/null
+++ b/src/test/java/org/jenkinsci/plugins/pitmutation/PitLoggerTest.java
@@ -0,0 +1,83 @@
+package org.jenkinsci.plugins.pitmutation;
+
+import hudson.FilePath;
+import hudson.model.TaskListener;
+import org.jenkinsci.plugins.pitmutation.targets.MutationResult;
+import org.jenkinsci.plugins.pitmutation.targets.MutationStats;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.io.File;
+import java.io.PrintStream;
+
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class PitLoggerTest {
+
+ @Mock
+ private TaskListener listener;
+ @Mock
+ private PitBuildAction action;
+ @Mock
+ private MutationResult report;
+ @Mock
+ private MutationStats stats;
+ @Mock
+ private PrintStream printStream;
+
+ private PitLogger pitLogger;
+
+ @BeforeEach
+ void setUp() {
+ pitLogger = new PitLogger();
+
+ when(listener.getLogger()).thenReturn(printStream);
+ }
+
+ @Test
+ void logResults() {
+ when(action.getReport()).thenReturn(report);
+ when(action.getPreviousAction()).thenReturn(action);
+ when(report.getMutationStats()).thenReturn(stats);
+ when(stats.getKillPercent()).thenReturn(70.0f);
+
+ pitLogger.logResults(listener, action);
+
+ verify(printStream, atLeastOnce()).println(anyString());
+ }
+
+ @Test
+ void logMissingReportsIgnored() {
+ pitLogger.logMissingReportsIgnored(listener);
+ verify(printStream).println(anyString());
+ }
+
+ @Test
+ void logBuildFailedNoReports() {
+ pitLogger.logBuildFailedNoReports(listener);
+ verify(printStream).println(anyString());
+ }
+
+ @Test
+ void logLookingForReports() {
+ FilePath workspace = new FilePath(new File("path/to/workspace"));
+ pitLogger.logLookingForReports(listener, workspace);
+
+ verify(printStream).println(anyString());
+ }
+
+ @Test
+ void logPublishReport() {
+ FilePath workspace = new FilePath(new File("path/to/workspace"));
+ pitLogger.logPublishReport(listener, workspace);
+
+ verify(printStream).println(anyString());
+ }
+}
diff --git a/src/test/java/org/jenkinsci/plugins/pitmutation/PitProjectActionTest.java b/src/test/java/org/jenkinsci/plugins/pitmutation/PitProjectActionTest.java
new file mode 100644
index 0000000..2e022fa
--- /dev/null
+++ b/src/test/java/org/jenkinsci/plugins/pitmutation/PitProjectActionTest.java
@@ -0,0 +1,126 @@
+package org.jenkinsci.plugins.pitmutation;
+
+
+import hudson.model.AbstractBuild;
+import hudson.model.AbstractProject;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.kohsuke.stapler.StaplerRequest;
+import org.kohsuke.stapler.StaplerResponse;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.io.IOException;
+
+import static hudson.model.Result.SUCCESS;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+
+@ExtendWith(MockitoExtension.class)
+class PitProjectActionTest {
+
+ @Mock
+ private AbstractProject, ?> project;
+ @Mock
+ private AbstractBuild, ?> lastSuccessfulBuild;
+ @Mock
+ private AbstractBuild, ?> previousSuccessfulBuild;
+ @Mock
+ private AbstractBuild, ?> failedBuild;
+ @Mock
+ private PitBuildAction action;
+ @Mock
+ private StaplerRequest req;
+ @Mock
+ private StaplerResponse rsp;
+
+ private PitProjectAction pitProjectAction;
+
+ @BeforeEach
+ void setUp() {
+ pitProjectAction = new PitProjectAction(project);
+ }
+
+ @Test
+ void getLastResult_noLastResult() {
+ doReturn(null).when(project).getLastSuccessfulBuild();
+ PitBuildAction lastResult = pitProjectAction.getLastResult();
+ assertNull(lastResult);
+ }
+
+ @Test
+ void getLastResult_returnsLastResult() {
+ doReturn(lastSuccessfulBuild).when(project).getLastSuccessfulBuild();
+ doReturn(SUCCESS).when(lastSuccessfulBuild).getResult();
+ doReturn(action).when(lastSuccessfulBuild).getAction(PitBuildAction.class);
+
+ PitBuildAction lastResult = pitProjectAction.getLastResult();
+ assertEquals(action, lastResult);
+ }
+
+ @Test
+ void getLastResultBuild_returnsLastBuildNumber() {
+ doReturn(lastSuccessfulBuild).when(project).getLastSuccessfulBuild();
+ doReturn(action).when(lastSuccessfulBuild).getAction(PitBuildAction.class);
+ doReturn(10).when(lastSuccessfulBuild).getNumber();
+
+ Integer lastResultBuild = pitProjectAction.getLastResultBuild();
+ assertEquals(10, lastResultBuild);
+ }
+
+ @Test
+ void getIconFileName_returnsCorrectPath() {
+ String iconPath = pitProjectAction.getIconFileName();
+ assertEquals("/plugin/pitmutation/pitest.png", iconPath);
+ }
+
+ @Test
+ void getDisplayName_returnsCorrectName() {
+ String name = pitProjectAction.getDisplayName();
+ assertEquals("PIT Mutation Report", name);
+ }
+
+ @Test
+ void getSearchUrl_returnsCorrectUrl() {
+ String url = pitProjectAction.getSearchUrl();
+ assertEquals("pitmutation", url);
+ }
+
+ @Test
+ void isFloatingBoxActive_returnsTrue() {
+ assertTrue(pitProjectAction.isFloatingBoxActive());
+ }
+
+ @Test
+ void doIndex_redirectsCorrectly() throws IOException {
+ doReturn(lastSuccessfulBuild).when(project).getLastSuccessfulBuild();
+ doReturn(action).when(lastSuccessfulBuild).getAction(PitBuildAction.class);
+ doReturn(10).when(lastSuccessfulBuild).getNumber();
+
+ pitProjectAction.doIndex(req, rsp);
+
+ verify(rsp).sendRedirect2("../10/pitmutation");
+ }
+
+ @Test
+ void doIndex_buildNumberNull_redirectsCorrectly() throws IOException {
+ doReturn(null).when(project).getLastSuccessfulBuild();
+ pitProjectAction.doIndex(req, rsp);
+ verify(rsp).sendRedirect2("nodata");
+ }
+
+ @Test
+ void doGraph_CallsDoGraphOnLastResult() throws IOException {
+ doReturn(lastSuccessfulBuild).when(project).getLastSuccessfulBuild();
+ doReturn(action).when(lastSuccessfulBuild).getAction(PitBuildAction.class);
+
+ pitProjectAction.doGraph(req, rsp);
+
+ verify(action).doGraph(req, rsp);
+ }
+}
diff --git a/src/test/java/org/jenkinsci/plugins/pitmutation/PitPublisherTest.java b/src/test/java/org/jenkinsci/plugins/pitmutation/PitPublisherTest.java
index 2bedfe1..97e0083 100644
--- a/src/test/java/org/jenkinsci/plugins/pitmutation/PitPublisherTest.java
+++ b/src/test/java/org/jenkinsci/plugins/pitmutation/PitPublisherTest.java
@@ -1,22 +1,45 @@
package org.jenkinsci.plugins.pitmutation;
-import hudson.model.Result;
+import hudson.EnvVars;
+import hudson.FilePath;
+import hudson.Launcher;
+import hudson.model.AbstractProject;
+import hudson.model.Action;
+import hudson.model.Project;
+import hudson.model.Run;
+import hudson.model.TaskListener;
import org.jenkinsci.plugins.pitmutation.targets.MutationStats;
import org.jenkinsci.plugins.pitmutation.targets.ProjectMutations;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.jupiter.MockitoExtension;
-import static org.junit.Assert.*;
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+import static hudson.model.Result.FAILURE;
+import static hudson.model.Result.SUCCESS;
+import static hudson.tasks.BuildStepMonitor.STEP;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+
/**
* User: Ed Kimber Date: 17/03/13 Time: 17:55
*/
-public class PitPublisherTest {
+@ExtendWith(MockitoExtension.class)
+class PitPublisherTest {
private static final float MINIMUM_KILL_RATIO = 0.25f;
@@ -26,112 +49,228 @@ public class PitPublisherTest {
private ProjectMutations report;
@Mock
private MutationStats stats;
+ @Mock
+ private PitLogger pitLogger;
+
+ @Mock
+ private FileProcessor fileProcessor;
- private PitPublisher publisher;
- private PitPublisherTSS publisherTSS;
+ @Mock
+ private ResultDecider resultDecider;
- static class PitPublisherTSS extends PitPublisher {
- boolean infoImproveLogged = false;
- boolean infoPercentLogged = false;
+ @Test
+ void ignoreMissingReports_ignore_noReports() throws Exception {
+ FileProcessor fileProcessor = mock(FileProcessor.class);
+ ResultDecider decider = mock(ResultDecider.class);
+ PitPublisher publisher =
+ new PitPublisher("**/mutations.xml", MINIMUM_KILL_RATIO, true, true, fileProcessor, decider, pitLogger);
+ TaskListener taskListener = mock(TaskListener.class);
+ Run, ?> build = mock(Run.class);
+ publisher.perform(build, mock(FilePath.class), mock(EnvVars.class), mock(Launcher.class), taskListener);
- public PitPublisherTSS(String mutationStatsFile, float minimumKillRatio, boolean killRatioMustImprove) {
- super(mutationStatsFile, minimumKillRatio, killRatioMustImprove);
- }
+ verify(build).setResult(SUCCESS);
+ verify(pitLogger).logMissingReportsIgnored(any());
+ }
- class MustImproveConditionTSS extends MustImproveCondition {
- void logInfo(final PitBuildAction action, MutationStats stats) {
- infoImproveLogged = true;
- }
- }
+ @Test
+ void ignoreMissingReports_doNotIgnore_noReports() throws Exception {
+ FileProcessor fileProcessor = mock(FileProcessor.class);
+ ResultDecider decider = mock(ResultDecider.class);
+ PitPublisher publisher =
+ new PitPublisher("**/mutations.xml", MINIMUM_KILL_RATIO, true, false, fileProcessor, decider, pitLogger);
+ TaskListener taskListener = mock(TaskListener.class);
+ Run, ?> build = mock(Run.class);
+ FilePath workspace = mock(FilePath.class);
+ when(workspace.act(any(FilePath.FileCallable.class))).thenReturn(new FilePath[]{mock(FilePath.class),
+ mock(FilePath.class)});
- Condition mustImprove() {
- return new MustImproveConditionTSS();
- }
+ publisher.perform(build, workspace, mock(EnvVars.class), mock(Launcher.class), taskListener);
- class PercentageThresholdConditionTSS extends PercentageThresholdCondition {
- PercentageThresholdConditionTSS(float percentage) {
- super(percentage);
- }
+ verify(build).setResult(FAILURE);
+ verify(pitLogger).logBuildFailedNoReports(any());
+ }
- void dologging(MutationStats stats) {
- infoPercentLogged = true;
- }
- }
+ @Test
+ void oneReport_ioException() throws Exception {
+ FileProcessor fileProcessor = mock(FileProcessor.class);
+ ResultDecider decider = mock(ResultDecider.class);
+ PitPublisher publisher = new PitPublisher(
+ "./src/test/resources/org/jenkinsci/plugins/pitmutation/testmutations-00.xml",
+ MINIMUM_KILL_RATIO,
+ true,
+ false,
+ fileProcessor,
+ decider,
+ pitLogger);
+ TaskListener taskListener = mock(TaskListener.class);
+ FilePath workspace = mock(FilePath.class);
+ FilePath report = mock(FilePath.class);
+ when(report.exists()).thenReturn(true);
+ when(report.isDirectory()).thenReturn(false);
+ FilePath[] reports = {report};
+ when(workspace.act(any(ParseReportCallable.class))).thenReturn(reports);
+ doThrow(IOException.class).when(fileProcessor).copySingleModuleReport(any(), any());
+ Run, ?> build = mock(Run.class);
+ when(build.getRootDir()).thenReturn(new File("src/test/resources"));
+ publisher.perform(build, workspace, mock(EnvVars.class), mock(Launcher.class), taskListener);
- Condition percentageThreshold(final float percentage) {
- return new PercentageThresholdConditionTSS(percentage);
- }
+ verify(build).setResult(FAILURE);
+ verify(pitLogger, never()).logResults(any(), any());
}
- @Before
- public void setup() {
- MockitoAnnotations.openMocks(this);
+ @Test
+ void oneReport() throws Exception {
+ FileProcessor fileProcessor = mock(FileProcessor.class);
+ ResultDecider decider = mock(ResultDecider.class);
+ PitPublisher publisher = new PitPublisher(
+ "./src/test/resources/org/jenkinsci/plugins/pitmutation/testmutations-00.xml",
+ MINIMUM_KILL_RATIO,
+ true,
+ false,
+ fileProcessor,
+ decider,
+ pitLogger);
+ TaskListener taskListener = mock(TaskListener.class);
+ FilePath workspace = mock(FilePath.class);
+ FilePath report = mock(FilePath.class);
+ when(report.exists()).thenReturn(true);
+ when(report.isDirectory()).thenReturn(false);
+ FilePath[] reports = {report};
+ when(workspace.act(any(ParseReportCallable.class))).thenReturn(reports);
+ doNothing().when(fileProcessor).copySingleModuleReport(any(), any());
+ Run, ?> build = mock(Run.class);
+ when(build.getRootDir()).thenReturn(new File("src/test/resources"));
+ when(decider.decideBuildResult(any())).thenReturn(SUCCESS);
+ publisher.perform(build, workspace, mock(EnvVars.class), mock(Launcher.class), taskListener);
+
+ verify(build).setResult(SUCCESS);
+ verify(pitLogger).logResults(any(), any());
}
+
@Test
- public void testDecideBuildResultFailsIfKillRatioTooLow() {
- publisherTSS = createPitPublisher(false);
- publisher = publisherTSS;
- final float TOO_LOW_RATIO = 0.2f;
- setupBoilderPlateMocks();
- Mockito.when(stats.getKillPercent()).thenReturn(TOO_LOW_RATIO);
- assertEquals(Result.FAILURE, publisher.decideBuildResult(action));
- assertFalse(publisherTSS.infoImproveLogged);
- assertTrue(publisherTSS.infoPercentLogged);
+ void multiModuleReports() throws Exception {
+ FileProcessor fileProcessor = mock(FileProcessor.class);
+ ResultDecider decider = mock(ResultDecider.class);
+ PitPublisher publisher = new PitPublisher(
+ "./src/test/resources/org/jenkinsci/plugins/pitmutation/testmutations-00.xml",
+ MINIMUM_KILL_RATIO,
+ true,
+ false,
+ fileProcessor,
+ decider,
+ pitLogger);
+ TaskListener taskListener = mock(TaskListener.class);
+ FilePath workspace = mock(FilePath.class);
+ FilePath report1 = mock(FilePath.class);
+ when(report1.exists()).thenReturn(true);
+ when(report1.isDirectory()).thenReturn(false);
+ FilePath report2 = mock(FilePath.class);
+ when(report2.exists()).thenReturn(true);
+ when(report2.isDirectory()).thenReturn(false);
+
+ FilePath[] reports = {report1, report2};
+ when(workspace.act(any(ParseReportCallable.class))).thenReturn(reports);
+ when(fileProcessor.getNames(any(), any())).thenReturn(Map.of(mock(FilePath.class),
+ "1",
+ mock(FilePath.class),
+ "2"));
+ doNothing().when(fileProcessor).copyMultiModuleReport(any(), any(), any());
+ Run, ?> build = mock(Run.class);
+ when(build.getRootDir()).thenReturn(new File("src/test/resources"));
+ when(decider.decideBuildResult(any())).thenReturn(SUCCESS);
+ publisher.perform(build, workspace, mock(EnvVars.class), mock(Launcher.class), taskListener);
+
+ verify(build).setResult(SUCCESS);
+ verify(pitLogger).logResults(any(), any());
}
+
@Test
- public void testDecideBuildResultSuccessIfKillRatioSame() {
- publisher = createPitPublisher(false);
- final float BIGGER_RATIO = MINIMUM_KILL_RATIO + 0.2f;
- setupBoilderPlateMocks();
- Mockito.when(stats.getKillPercent()).thenReturn(MINIMUM_KILL_RATIO, BIGGER_RATIO);
- assertEquals(Result.SUCCESS, publisher.decideBuildResult(action));
- assertEquals(Result.SUCCESS, publisher.decideBuildResult(action));
+ void multiModuleReports_ioException() throws Exception {
+ FileProcessor fileProcessor = mock(FileProcessor.class);
+ ResultDecider decider = mock(ResultDecider.class);
+ PitPublisher publisher = new PitPublisher(
+ "./src/test/resources/org/jenkinsci/plugins/pitmutation/testmutations-00.xml",
+ MINIMUM_KILL_RATIO,
+ true,
+ false,
+ fileProcessor,
+ decider,
+ pitLogger);
+ TaskListener taskListener = mock(TaskListener.class);
+ FilePath workspace = mock(FilePath.class);
+ FilePath report1 = mock(FilePath.class);
+ when(report1.exists()).thenReturn(true);
+ when(report1.isDirectory()).thenReturn(false);
+
+ FilePath report2 = mock(FilePath.class);
+ when(report2.exists()).thenReturn(true);
+ when(report2.isDirectory()).thenReturn(false);
+
+ FilePath[] reports = {report1, report2};
+ when(workspace.act(any(ParseReportCallable.class))).thenReturn(reports);
+ when(fileProcessor.getNames(any(), any())).thenReturn(Map.of(mock(FilePath.class),
+ "1",
+ mock(FilePath.class),
+ "2"));
+ doThrow(IOException.class).when(fileProcessor).copyMultiModuleReport(any(), any(), any());
+ Run, ?> build = mock(Run.class);
+ when(build.getRootDir()).thenReturn(new File("src/test/resources"));
+ publisher.perform(build, workspace, mock(EnvVars.class), mock(Launcher.class), taskListener);
+
+ verify(build).setResult(FAILURE);
+ verify(pitLogger, never()).logResults(any(), any());
}
@Test
- public void testMustImproveConditionSucceedsWhenPreviousActionIsNull() {
- publisher = createPitPublisher(true);
- final float BIGGER_RATIO = MINIMUM_KILL_RATIO + 0.2f;
- setupBoilderPlateMocks();
- Mockito.when(stats.getKillPercent()).thenReturn(BIGGER_RATIO);
- assertEquals(Result.SUCCESS, publisher.decideBuildResult(action));
+ void getMinimumKillRatio() {
+ PitPublisher pitPublisher = new PitPublisher("target/pit-reports/mutations.xml", 0.5f, false, false);
+ assertEquals(0.5f, pitPublisher.getMinimumKillRatio());
}
@Test
- public void testMustImproveConditionFailsWhenWhenPreviousActionKillRatioIsBetter() {
- publisher = createPitPublisher(true);
- setupBoilderPlateMocks();
- setupPercentKillRatioOK();
- PitBuildAction prevAction = mock(PitBuildAction.class);
- ProjectMutations prevReport_ = Mockito.mock(ProjectMutations.class);
- MutationStats prevStats_ = Mockito.mock(MutationStats.class);
- when(action.getPreviousAction()).thenReturn(prevAction);
- when(prevAction.getReport()).thenReturn(prevReport_);
- when(prevReport_.getMutationStats()).thenReturn(prevStats_);
- when(stats.getKillPercent()).thenReturn(MINIMUM_KILL_RATIO + 0.5f);
- when(prevStats_.getKillPercent()).thenReturn(MINIMUM_KILL_RATIO + 0.6f, // previous is better
- MINIMUM_KILL_RATIO + 0.5f, // equal
- MINIMUM_KILL_RATIO + 0.4f); //previous is worse
-
- assertEquals(Result.UNSTABLE, publisher.decideBuildResult(action)); //previous is better
- assertEquals(Result.SUCCESS, publisher.decideBuildResult(action)); // previous is equal
- assertEquals(Result.SUCCESS, publisher.decideBuildResult(action)); //previous is worse
+ void getKillRatioMustImprove() {
+ PitPublisher pitPublisher = new PitPublisher("target/pit-reports/mutations.xml", 0.5f, true, false);
+ assertTrue(pitPublisher.getKillRatioMustImprove());
}
- private void setupPercentKillRatioOK() {
- final float BIGGER_RATIO = MINIMUM_KILL_RATIO + 0.2f;
- when(stats.getKillPercent()).thenReturn(BIGGER_RATIO);
+ @Test
+ void getIgnoreMissingReports() {
+ PitPublisher pitPublisher = new PitPublisher("target/pit-reports/mutations.xml", 0.5f, false, true);
+ assertTrue(pitPublisher.getIgnoreMissingReports());
}
- private PitPublisherTSS createPitPublisher(boolean mustImprove) {
- return new PitPublisherTSS("**/mutations.xml", MINIMUM_KILL_RATIO, mustImprove);
+ @Test
+ void setIgnoreMissingReports_true() {
+ PitPublisher pitPublisher = new PitPublisher("target/pit-reports/mutations.xml", 0.5f, false, false);
+ pitPublisher.setIgnoreMissingReports(true);
+ assertTrue(pitPublisher.getIgnoreMissingReports());
}
- private void setupBoilderPlateMocks() {
- Mockito.when(action.getReport()).thenReturn(report);
- Mockito.when(report.getMutationStats()).thenReturn(stats);
+ @Test
+ void setIgnoreMissingReports_false() {
+ PitPublisher pitPublisher = new PitPublisher("target/pit-reports/mutations.xml", 0.5f, false, true);
+ pitPublisher.setIgnoreMissingReports(false);
+ assertFalse(pitPublisher.getIgnoreMissingReports());
}
+ @Test
+ void getProjectAction() {
+ PitPublisher pitPublisher = new PitPublisher("target/pit-reports/mutations.xml", 0.5f, false, true);
+ Action projectAction = pitPublisher.getProjectAction((AbstractProject, ?>) mock(Project.class));
+ assertNotNull(projectAction);
+ }
+
+ @Test
+ void getMutationStatsFile() {
+ PitPublisher pitPublisher = new PitPublisher("target/pit-reports/mutations.xml", 0.5f, false, true);
+ assertEquals("target/pit-reports/mutations.xml", pitPublisher.getMutationStatsFile());
+ }
+
+ @Test
+ void getRequiredMonitorService() {
+ PitPublisher pitPublisher = new PitPublisher("target/pit-reports/mutations.xml", 0.5f, false, true);
+ assertEquals(STEP, pitPublisher.getRequiredMonitorService());
+ }
}
diff --git a/src/test/java/org/jenkinsci/plugins/pitmutation/ResultDeciderTest.java b/src/test/java/org/jenkinsci/plugins/pitmutation/ResultDeciderTest.java
new file mode 100644
index 0000000..78efd6a
--- /dev/null
+++ b/src/test/java/org/jenkinsci/plugins/pitmutation/ResultDeciderTest.java
@@ -0,0 +1,49 @@
+package org.jenkinsci.plugins.pitmutation;
+
+import hudson.model.Result;
+import org.junit.jupiter.api.DynamicTest;
+import org.junit.jupiter.api.TestFactory;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.stream.Stream;
+
+import static hudson.model.Result.FAILURE;
+import static hudson.model.Result.SUCCESS;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class ResultDeciderTest {
+
+ @TestFactory
+ Stream testFactory() {
+ // Create a collection of inputs for the test.
+ Collection
@@ -15,7 +17,9 @@
<init>
23
org.pitest.mutationtest.engine.gregor.mutators.NonVoidMethodCallMutator
- 2
+
+ 2
+
@@ -24,7 +28,9 @@
<init>
26
org.pitest.mutationtest.engine.gregor.mutators.NonVoidMethodCallMutator
- 7
+
+ 7
+
@@ -34,7 +40,9 @@
equals
15
org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator
- 0
+
+ 0
+
@@ -43,7 +51,9 @@
setMutatedMethod
60
org.pitest.mutationtest.engine.gregor.mutators.experimental.MemberVariableMutator
- 4
+
+ 4
+
@@ -52,7 +62,9 @@
setStatus
36
org.pitest.mutationtest.engine.gregor.mutators.experimental.MemberVariableMutator
- 1
+
+ 1
+
org.jenkinsci.plugins.pitmutation.MutationReportTest.canDigestAMutation(org.jenkinsci.plugins.pitmutation.MutationReportTest)
@@ -64,7 +76,9 @@
<init>
18
org.pitest.mutationtest.engine.gregor.mutators.NonVoidMethodCallMutator
- 0
+
+ 0
+
@@ -73,7 +87,9 @@
<init>
16
org.pitest.mutationtest.engine.gregor.mutators.experimental.MemberVariableMutator
- 0
+
+ 0
+
org.jenkinsci.plugins.pitmutation.PitBuildActionTest.previousReturnsNullIfNoPreviousBuilds(org.jenkinsci.plugins.pitmutation.PitBuildActionTest)
@@ -84,7 +100,9 @@
<init>
18
org.pitest.mutationtest.engine.gregor.mutators.experimental.MemberVariableMutator
- 2
+
+ 2
+
@@ -93,7 +111,9 @@
<init>
17
org.pitest.mutationtest.engine.gregor.mutators.experimental.MemberVariableMutator
- 1
+
+ 1
+
@@ -102,7 +122,9 @@
getPreviousAction
29
org.pitest.mutationtest.engine.gregor.mutators.NonVoidMethodCallMutator
- 3
+
+ 3
+
org.jenkinsci.plugins.pitmutation.PitBuildActionTest.previousReturnsLastSuccessfulBuild(org.jenkinsci.plugins.pitmutation.PitBuildActionTest)
@@ -113,7 +135,9 @@
getBuildHealth
40
org.pitest.mutationtest.engine.gregor.mutators.ConstructorCallMutator
- 0
+
+ 0
+
@@ -122,7 +146,9 @@
getPreviousAction
27
org.pitest.mutationtest.engine.gregor.mutators.NonVoidMethodCallMutator
- 2
+
+ 2
+
@@ -132,7 +158,9 @@
digestMutations
42
org.pitest.mutationtest.engine.gregor.mutators.experimental.InlineConstantMutator
- 2
+
+ 2
+
@@ -141,7 +169,9 @@
digestMutations
39
org.pitest.mutationtest.engine.gregor.mutators.InlineConstantMutator
- 1
+
+ 1
+
@@ -150,7 +180,9 @@
digestMutations
38
org.pitest.mutationtest.engine.gregor.mutators.experimental.InlineConstantMutator
- 0
+
+ 0
+
@@ -159,7 +191,9 @@
digestMutations
29
org.pitest.mutationtest.engine.gregor.mutators.ConstructorCallMutator
- 1
+
+ 1
+
org.jenkinsci.plugins.pitmutation.MutationReportTest.canDigestAMutation(org.jenkinsci.plugins.pitmutation.MutationReportTest)
@@ -170,7 +204,9 @@
digestMutations
39
org.pitest.mutationtest.engine.gregor.mutators.experimental.InlineConstantMutator
- 1
+
+ 1
+
@@ -180,7 +216,9 @@
digestMutations
45
org.pitest.mutationtest.engine.gregor.mutators.ConstructorCallMutator
- 5
+
+ 5
+
org.jenkinsci.plugins.pitmutation.MutationReportTest.countsKills(org.jenkinsci.plugins.pitmutation.MutationReportTest)
diff --git a/src/test/resources/org/jenkinsci/plugins/pitmutation/testmutations-02.xml b/src/test/resources/org/jenkinsci/plugins/pitmutation/testmutations-02.xml
index 8ecabf7..4c9e217 100644
--- a/src/test/resources/org/jenkinsci/plugins/pitmutation/testmutations-02.xml
+++ b/src/test/resources/org/jenkinsci/plugins/pitmutation/testmutations-02.xml
@@ -9,7 +9,9 @@
332
org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator
- 5
+
+ 5
+
negated conditional
@@ -22,7 +24,9 @@
342
org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator
- 47
+
+ 47
+
es.rodri.MSInvoiceDetailTest.testController7(es.rodri.MSInvoiceDetailTest)
negated conditional
@@ -35,7 +39,9 @@
344
org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator
- 56
+
+ 56
+
negated conditional
@@ -48,7 +54,9 @@
344
org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator
- 59
+
+ 59
+
negated conditional