From 7d82b176899a925875879cbc8bc98ddb372f40f5 Mon Sep 17 00:00:00 2001 From: John Freeman Date: Sun, 5 Feb 2017 02:22:16 -0500 Subject: [PATCH 1/3] Add PIT report fixture Add the source files for a small Java project, and a PIT XML report generated from its tests, as fixtures. The project is kt3k's own BankAccount-DCI at commit cca6b9c5803aaec5175d0cb725ea469d213752f1. https://github.com/kt3k/BankAccount-DCI/tree/cca6b9c5803aaec5175d0cb725ea469d213752f1 --- src/test/fixture/mutations.xml | 91 +++++++++++++++++++ .../org/kt3k/bankaccount/BankAccount.java | 30 ++++++ .../org/kt3k/bankaccount/TransferContext.java | 45 +++++++++ 3 files changed, 166 insertions(+) create mode 100644 src/test/fixture/mutations.xml create mode 100644 src/test/fixture/org/kt3k/bankaccount/BankAccount.java create mode 100644 src/test/fixture/org/kt3k/bankaccount/TransferContext.java diff --git a/src/test/fixture/mutations.xml b/src/test/fixture/mutations.xml new file mode 100644 index 0000000..17e8458 --- /dev/null +++ b/src/test/fixture/mutations.xml @@ -0,0 +1,91 @@ + + + + BankAccount.java + org.kt3k.bankaccount.BankAccount + decrease + (Ljava/lang/Integer;)V + 19 + org.pitest.mutationtest.engine.gregor.mutators.MathMutator + 11 + org.kt3k.bankaccount.TransferContextTest.testTransfer(org.kt3k.bankaccount.TransferContextTest) + Replaced integer subtraction with addition + + + BankAccount.java + org.kt3k.bankaccount.BankAccount + getBalance + ()Ljava/lang/Integer; + 23 + org.pitest.mutationtest.engine.gregor.mutators.ReturnValsMutator + 5 + org.kt3k.bankaccount.TransferContextTest.testTransfer(org.kt3k.bankaccount.TransferContextTest) + mutated return of Object value for org/kt3k/bankaccount/BankAccount::getBalance to ( if (x != null) null else throw new RuntimeException ) + + + BankAccount.java + org.kt3k.bankaccount.BankAccount + getId + ()Ljava/lang/String; + 27 + org.pitest.mutationtest.engine.gregor.mutators.ReturnValsMutator + 5 + + mutated return of Object value for org/kt3k/bankaccount/BankAccount::getId to ( if (x != null) null else throw new RuntimeException ) + + + BankAccount.java + org.kt3k.bankaccount.BankAccount + increase + (Ljava/lang/Integer;)V + 15 + org.pitest.mutationtest.engine.gregor.mutators.MathMutator + 11 + org.kt3k.bankaccount.TransferContextTest.testTransfer(org.kt3k.bankaccount.TransferContextTest) + Replaced integer addition with subtraction + + + TransferContext.java + org.kt3k.bankaccount.TransferContext$BankAccountSender + send + (Ljava/lang/Integer;Lorg/kt3k/bankaccount/TransferContext$BankAccountReceiver;)V + 22 + org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator + 6 + org.kt3k.bankaccount.TransferContextTest.testTransfer(org.kt3k.bankaccount.TransferContextTest) + removed call to org/kt3k/bankaccount/BankAccount::decrease + + + TransferContext.java + org.kt3k.bankaccount.TransferContext$BankAccountSender + send + (Ljava/lang/Integer;Lorg/kt3k/bankaccount/TransferContext$BankAccountReceiver;)V + 24 + org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator + 11 + org.kt3k.bankaccount.TransferContextTest.testTransfer(org.kt3k.bankaccount.TransferContextTest) + removed call to org/kt3k/bankaccount/TransferContext$BankAccountReceiver::onReceive + + + TransferContext.java + org.kt3k.bankaccount.TransferContext + transfer + (Ljava/lang/Integer;)V + 42 + org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator + 8 + org.kt3k.bankaccount.TransferContextTest.testTransfer(org.kt3k.bankaccount.TransferContextTest) + removed call to org/kt3k/bankaccount/TransferContext$BankAccountSender::send + + + TransferContext.java + org.kt3k.bankaccount.TransferContext$BankAccountReceiver + onReceive + (Ljava/lang/Integer;)V + 37 + org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator + 6 + org.kt3k.bankaccount.TransferContextTest.testTransfer(org.kt3k.bankaccount.TransferContextTest) + removed call to org/kt3k/bankaccount/BankAccount::increase + + diff --git a/src/test/fixture/org/kt3k/bankaccount/BankAccount.java b/src/test/fixture/org/kt3k/bankaccount/BankAccount.java new file mode 100644 index 0000000..2c5a4eb --- /dev/null +++ b/src/test/fixture/org/kt3k/bankaccount/BankAccount.java @@ -0,0 +1,30 @@ +package org.kt3k.bankaccount; + +public class BankAccount { + + private String id; + private Integer balance; + + public BankAccount(String id, Integer balance) { + this.id = id; + this.balance = balance; + } + + + public void increase(Integer money) { + this.balance += money; + } + + public void decrease(Integer money) { + this.balance -= money; + } + + public Integer getBalance() { + return this.balance; + } + + public String getId() { + return this.id; + } + +} diff --git a/src/test/fixture/org/kt3k/bankaccount/TransferContext.java b/src/test/fixture/org/kt3k/bankaccount/TransferContext.java new file mode 100644 index 0000000..9761395 --- /dev/null +++ b/src/test/fixture/org/kt3k/bankaccount/TransferContext.java @@ -0,0 +1,45 @@ +package org.kt3k.bankaccount; + +public class TransferContext { + + private BankAccountSender sender; + private BankAccountReceiver receiver; + + public TransferContext(BankAccount sender, BankAccount receiver) { + this.sender = new BankAccountSender(sender); + this.receiver = new BankAccountReceiver(receiver); + } + + static private class BankAccountSender { + + private BankAccount actor; + + public BankAccountSender(BankAccount actor) { + this.actor = actor; + } + + public void send(Integer money, BankAccountReceiver receiver) { + this.actor.decrease(money); + + receiver.onReceive(money); + } + } + + static private class BankAccountReceiver { + + private BankAccount actor; + + public BankAccountReceiver(BankAccount actor) { + this.actor = actor; + } + + public void onReceive(Integer money) { + this.actor.increase(money); + } + } + + public void transfer(Integer money) { + this.sender.send(money, this.receiver); + } + +} From b681e8f47ae080facae51ddf21baa4836bea00b3 Mon Sep 17 00:00:00 2001 From: John Freeman Date: Sun, 5 Feb 2017 04:53:48 -0500 Subject: [PATCH 2/3] Support PIT reports in XML --- README.md | 34 +++++++ .../plugin/CoverallsPluginExtension.groovy | 3 + .../plugin/coveralls/CoverallsTask.groovy | 1 + .../domain/PITSourceReportFactory.groovy | 90 +++++++++++++++++++ .../domain/PITSourceReportFactoryTest.groovy | 42 +++++++++ 5 files changed, 170 insertions(+) create mode 100644 src/main/groovy/org/kt3k/gradle/plugin/coveralls/domain/PITSourceReportFactory.groovy create mode 100644 src/test/groovy/org/kt3k/gradle/plugin/coveralls/domain/PITSourceReportFactoryTest.groovy diff --git a/README.md b/README.md index 839ce3a..298d361 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,40 @@ after_success: - ./gradlew jacocoTestReport coveralls ``` +### Use with [*pitest*](https://github.com/szpak/gradle-pitest-plugin) plugin + +Add the following lines to build.gradle: + +```groovy +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'info.solidsoft.gradle.pitest:gradle-pitest-plugin:1.1.11' + } +} + +apply plugin: 'info.solidsoft.pitest' + +pitest { + timestampedReports = false + outputFormats = ['XML'] +} +``` + +An example `.travis.yml` looks like following: + +```yaml +language: java + +jdk: +- oraclejdk8 + +after_success: +- ./gradlew pitest coveralls +``` + ### Use with Travis-CI Pro & Coveralls Pro When using Travis-CI Pro, you must provide your Coveralls Pro repo token in the diff --git a/src/main/groovy/org/kt3k/gradle/plugin/CoverallsPluginExtension.groovy b/src/main/groovy/org/kt3k/gradle/plugin/CoverallsPluginExtension.groovy index 9f98618..f92b59e 100644 --- a/src/main/groovy/org/kt3k/gradle/plugin/CoverallsPluginExtension.groovy +++ b/src/main/groovy/org/kt3k/gradle/plugin/CoverallsPluginExtension.groovy @@ -22,6 +22,9 @@ class CoverallsPluginExtension { /** Cobertura report path */ Object coberturaReportPath = 'build/reports/cobertura/coverage.xml' + /** PIT report path */ + Object pitReportPath = 'build/reports/pitest/mutations.xml' + /** * Additional source directories */ diff --git a/src/main/groovy/org/kt3k/gradle/plugin/coveralls/CoverallsTask.groovy b/src/main/groovy/org/kt3k/gradle/plugin/coveralls/CoverallsTask.groovy index df744fe..d82f14a 100644 --- a/src/main/groovy/org/kt3k/gradle/plugin/coveralls/CoverallsTask.groovy +++ b/src/main/groovy/org/kt3k/gradle/plugin/coveralls/CoverallsTask.groovy @@ -118,6 +118,7 @@ class CoverallsTask extends DefaultTask { CoverallsPluginExtension coveralls = this.project.extensions.getByType(CoverallsPluginExtension) this.sourceReportFactoryMap[this.project.file(coveralls.coberturaReportPath).absolutePath] = new CoberturaSourceReportFactory() this.sourceReportFactoryMap[this.project.file(coveralls.jacocoReportPath).absolutePath] = new JacocoSourceReportFactory() + this.sourceReportFactoryMap[this.project.file(coveralls.pitReportPath).absolutePath] = new PITSourceReportFactory() // search the coverage file Map.Entry entry = this.sourceReportFactoryMap.find { Map.Entry entry -> diff --git a/src/main/groovy/org/kt3k/gradle/plugin/coveralls/domain/PITSourceReportFactory.groovy b/src/main/groovy/org/kt3k/gradle/plugin/coveralls/domain/PITSourceReportFactory.groovy new file mode 100644 index 0000000..ef8abc8 --- /dev/null +++ b/src/main/groovy/org/kt3k/gradle/plugin/coveralls/domain/PITSourceReportFactory.groovy @@ -0,0 +1,90 @@ +package org.kt3k.gradle.plugin.coveralls.domain + +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPlugin + +/** + * Factory class for SourceReport for PIT report file. + */ +class PITSourceReportFactory implements SourceReportFactory { + + @Override + List createReportList(Project project, File reportFile) { + // Build a PATH of source directories. + List sourceDirectories = project.extensions.coveralls.sourceDirs + + project.plugins.withType(JavaPlugin) { + sourceDirectories += project.sourceSets.main.java.srcDirs + } + + return createReportList(sourceDirectories, reportFile) + } + + // This method has been separated for testing. + static List createReportList( + List sourceDirectories, File reportFile) + { + def mutations = new XmlSlurper().parse(reportFile) + + // mapping of [filename] => [line] => [hits] + Map> hitsPerLineMapPerFile = [:] + + mutations.mutation.each() { mutation -> + // PIT reports only the source file's base name, which might not be + // unique. Assume the source file's directory can be derived from + // the package name. + // This pattern matches more than valid package names, + // but we're assuming the name is valid if it compiled. + def matcher = (mutation.mutatedClass.text() =~ /(?:[a-zA-Z0-9_]+\.)*/) + if (!matcher.find()) { + // Cannot parse the class name. + // TODO: Give a warning to the user at least. + return + } + def packageName = matcher.group(0) + def filename = packageName.replace('.', File.separator) + mutation.sourceFile.text() + def hitsPerLine = hitsPerLineMapPerFile.get(filename, [:]) + Integer lineNumber = mutation.lineNumber.text().toInteger() - 1 + Integer hits = hitsPerLine.get(lineNumber, 0) + hitsPerLine[lineNumber] = hits + 1 + } + + List reports = new ArrayList() + + hitsPerLineMapPerFile.each { String filename, Map hitsPerLine -> + + // find actual source file from directory candidates + String sourceFilename = findSourceFile(sourceDirectories, filename) + + if (sourceFilename == null) { + // if sourceFilename is not found then ignore the entry + return + } + + File sourceFile = new File(sourceFilename) + String sourceText = sourceFile.text + + // create hits per line list + List hitsArray = [null] * sourceText.readLines().size() + + hitsPerLine.each { Integer line, Integer hits -> + hitsArray[line] = hits + } + + reports.add new SourceReport(sourceFilename, sourceText, hitsArray) + } + + return reports + } + + /** + * Finds the actual source file path and returns File object + * + * @param sourceDirs the list of candidate source dirs + * @param filename the file name to search + * @return found File object + */ + private static String findSourceFile(List sourceDirs, String filename) { + return sourceDirs.collect { new File(it, filename) }.find { it.exists() } + } +} diff --git a/src/test/groovy/org/kt3k/gradle/plugin/coveralls/domain/PITSourceReportFactoryTest.groovy b/src/test/groovy/org/kt3k/gradle/plugin/coveralls/domain/PITSourceReportFactoryTest.groovy new file mode 100644 index 0000000..4359ae0 --- /dev/null +++ b/src/test/groovy/org/kt3k/gradle/plugin/coveralls/domain/PITSourceReportFactoryTest.groovy @@ -0,0 +1,42 @@ +package org.kt3k.gradle.plugin.coveralls.domain + +import org.gradle.api.Project +import org.gradle.testfixtures.ProjectBuilder +import org.junit.Before +import org.junit.Test +import static org.junit.Assert.* +import org.kt3k.gradle.plugin.CoverallsPluginExtension + +class PITSourceReportFactoryTest { + + Project project + + @Before + public void setUp() { + + // fake a project + project = ProjectBuilder.builder().build() + + // create coveralls extension + project.extensions.create('coveralls', CoverallsPluginExtension) + } + + @Test + public void testCreateFromMutationsXML() { + + List reports = PITSourceReportFactory.createReportList( + [new File('src/test/fixture')], + new File ('src/test/fixture/mutations.xml')) + + assertNotNull reports + assertEquals 2, reports.size() + + assertEquals 'src/test/fixture/org/kt3k/bankaccount/BankAccount.java', reports[0].name + assertEquals 30, reports[0].coverage.size() + assertEquals 4, reports[0].coverage.findAll{ it != null }.sum() + assertEquals 'src/test/fixture/org/kt3k/bankaccount/TransferContext.java', reports[1].name + assertEquals 45, reports[1].coverage.size() + assertEquals 4, reports[1].coverage.findAll{ it != null }.sum() + } + +} From 990414ce16cba90f7d1578359bfc4f8b57e69116 Mon Sep 17 00:00:00 2001 From: John Freeman Date: Sun, 5 Feb 2017 11:03:02 -0500 Subject: [PATCH 3/3] Fix tests --- .../gradle/plugin/coveralls/CoverallsTaskTest.groovy | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/test/groovy/org/kt3k/gradle/plugin/coveralls/CoverallsTaskTest.groovy b/src/test/groovy/org/kt3k/gradle/plugin/coveralls/CoverallsTaskTest.groovy index f78060c..7e1306a 100644 --- a/src/test/groovy/org/kt3k/gradle/plugin/coveralls/CoverallsTaskTest.groovy +++ b/src/test/groovy/org/kt3k/gradle/plugin/coveralls/CoverallsTaskTest.groovy @@ -53,8 +53,9 @@ class CoverallsTaskTest { Logger logger = task.logger = Mockito.mock Logger // set nonexistent report file paths - this.project.extensions.coveralls.coberturaReportPath = 'foo/bar.xml' - this.project.extensions.coveralls.jacocoReportPath = 'baz/bar.xml' + this.project.extensions.coveralls.coberturaReportPath = 'none/cobertura.xml' + this.project.extensions.coveralls.jacocoReportPath = 'none/jacoco.xml' + this.project.extensions.coveralls.pitReportPath = 'none/pit.xml' String projDir = this.project.projectDir.path @@ -62,7 +63,11 @@ class CoverallsTaskTest { task.coverallsAction() - Mockito.verify(logger).error 'No report file available: ' + [projDir + separatorChar + 'foo' + separatorChar + 'bar.xml', projDir + separatorChar + 'baz' + separatorChar + 'bar.xml'] + Mockito.verify(logger).error 'No report file available: ' + [ + projDir + separatorChar + 'none' + separatorChar + 'cobertura.xml', + projDir + separatorChar + 'none' + separatorChar + 'jacoco.xml', + projDir + separatorChar + 'none' + separatorChar + 'pit.xml', + ] } @Rule