Skip to content

Commit

Permalink
Merge pull request #71 from thejohnfreeman/master
Browse files Browse the repository at this point in the history
Support coverage calculated from PIT mutation tests
  • Loading branch information
kt3k committed Feb 5, 2017
2 parents d6d9543 + 990414c commit 4c6790b
Show file tree
Hide file tree
Showing 9 changed files with 344 additions and 3 deletions.
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, SourceReportFactory> entry = this.sourceReportFactoryMap.find { Map.Entry<String, SourceReportFactory> entry ->
Expand Down
Original file line number Diff line number Diff line change
@@ -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<SourceReport> createReportList(Project project, File reportFile) {
// Build a PATH of source directories.
List<File> 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<SourceReport> createReportList(
List<File> sourceDirectories, File reportFile)
{
def mutations = new XmlSlurper().parse(reportFile)

// mapping of [filename] => [line] => [hits]
Map<String, Map<Integer, Integer>> 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<SourceReport> reports = new ArrayList<SourceReport>()

hitsPerLineMapPerFile.each { String filename, Map<Integer, Integer> 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<Integer> 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<File> sourceDirs, String filename) {
return sourceDirs.collect { new File(it, filename) }.find { it.exists() }
}
}
91 changes: 91 additions & 0 deletions src/test/fixture/mutations.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<mutations>
<mutation detected="true" status="KILLED">
<sourceFile>BankAccount.java</sourceFile>
<mutatedClass>org.kt3k.bankaccount.BankAccount</mutatedClass>
<mutatedMethod>decrease</mutatedMethod>
<methodDescription>(Ljava/lang/Integer;)V</methodDescription>
<lineNumber>19</lineNumber>
<mutator>org.pitest.mutationtest.engine.gregor.mutators.MathMutator</mutator>
<index>11</index>
<killingTest>org.kt3k.bankaccount.TransferContextTest.testTransfer(org.kt3k.bankaccount.TransferContextTest)</killingTest>
<description>Replaced integer subtraction with addition</description>
</mutation>
<mutation detected="true" status="KILLED">
<sourceFile>BankAccount.java</sourceFile>
<mutatedClass>org.kt3k.bankaccount.BankAccount</mutatedClass>
<mutatedMethod>getBalance</mutatedMethod>
<methodDescription>()Ljava/lang/Integer;</methodDescription>
<lineNumber>23</lineNumber>
<mutator>org.pitest.mutationtest.engine.gregor.mutators.ReturnValsMutator</mutator>
<index>5</index>
<killingTest>org.kt3k.bankaccount.TransferContextTest.testTransfer(org.kt3k.bankaccount.TransferContextTest)</killingTest>
<description>mutated return of Object value for org/kt3k/bankaccount/BankAccount::getBalance to ( if (x != null) null else throw new RuntimeException )</description>
</mutation>
<mutation detected="false" status="NO_COVERAGE">
<sourceFile>BankAccount.java</sourceFile>
<mutatedClass>org.kt3k.bankaccount.BankAccount</mutatedClass>
<mutatedMethod>getId</mutatedMethod>
<methodDescription>()Ljava/lang/String;</methodDescription>
<lineNumber>27</lineNumber>
<mutator>org.pitest.mutationtest.engine.gregor.mutators.ReturnValsMutator</mutator>
<index>5</index>
<killingTest/>
<description>mutated return of Object value for org/kt3k/bankaccount/BankAccount::getId to ( if (x != null) null else throw new RuntimeException )</description>
</mutation>
<mutation detected="true" status="KILLED">
<sourceFile>BankAccount.java</sourceFile>
<mutatedClass>org.kt3k.bankaccount.BankAccount</mutatedClass>
<mutatedMethod>increase</mutatedMethod>
<methodDescription>(Ljava/lang/Integer;)V</methodDescription>
<lineNumber>15</lineNumber>
<mutator>org.pitest.mutationtest.engine.gregor.mutators.MathMutator</mutator>
<index>11</index>
<killingTest>org.kt3k.bankaccount.TransferContextTest.testTransfer(org.kt3k.bankaccount.TransferContextTest)</killingTest>
<description>Replaced integer addition with subtraction</description>
</mutation>
<mutation detected="true" status="KILLED">
<sourceFile>TransferContext.java</sourceFile>
<mutatedClass>org.kt3k.bankaccount.TransferContext$BankAccountSender</mutatedClass>
<mutatedMethod>send</mutatedMethod>
<methodDescription>(Ljava/lang/Integer;Lorg/kt3k/bankaccount/TransferContext$BankAccountReceiver;)V</methodDescription>
<lineNumber>22</lineNumber>
<mutator>org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator</mutator>
<index>6</index>
<killingTest>org.kt3k.bankaccount.TransferContextTest.testTransfer(org.kt3k.bankaccount.TransferContextTest)</killingTest>
<description>removed call to org/kt3k/bankaccount/BankAccount::decrease</description>
</mutation>
<mutation detected="true" status="KILLED">
<sourceFile>TransferContext.java</sourceFile>
<mutatedClass>org.kt3k.bankaccount.TransferContext$BankAccountSender</mutatedClass>
<mutatedMethod>send</mutatedMethod>
<methodDescription>(Ljava/lang/Integer;Lorg/kt3k/bankaccount/TransferContext$BankAccountReceiver;)V</methodDescription>
<lineNumber>24</lineNumber>
<mutator>org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator</mutator>
<index>11</index>
<killingTest>org.kt3k.bankaccount.TransferContextTest.testTransfer(org.kt3k.bankaccount.TransferContextTest)</killingTest>
<description>removed call to org/kt3k/bankaccount/TransferContext$BankAccountReceiver::onReceive</description>
</mutation>
<mutation detected="true" status="KILLED">
<sourceFile>TransferContext.java</sourceFile>
<mutatedClass>org.kt3k.bankaccount.TransferContext</mutatedClass>
<mutatedMethod>transfer</mutatedMethod>
<methodDescription>(Ljava/lang/Integer;)V</methodDescription>
<lineNumber>42</lineNumber>
<mutator>org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator</mutator>
<index>8</index>
<killingTest>org.kt3k.bankaccount.TransferContextTest.testTransfer(org.kt3k.bankaccount.TransferContextTest)</killingTest>
<description>removed call to org/kt3k/bankaccount/TransferContext$BankAccountSender::send</description>
</mutation>
<mutation detected="true" status="KILLED">
<sourceFile>TransferContext.java</sourceFile>
<mutatedClass>org.kt3k.bankaccount.TransferContext$BankAccountReceiver</mutatedClass>
<mutatedMethod>onReceive</mutatedMethod>
<methodDescription>(Ljava/lang/Integer;)V</methodDescription>
<lineNumber>37</lineNumber>
<mutator>org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator</mutator>
<index>6</index>
<killingTest>org.kt3k.bankaccount.TransferContextTest.testTransfer(org.kt3k.bankaccount.TransferContextTest)</killingTest>
<description>removed call to org/kt3k/bankaccount/BankAccount::increase</description>
</mutation>
</mutations>
30 changes: 30 additions & 0 deletions src/test/fixture/org/kt3k/bankaccount/BankAccount.java
Original file line number Diff line number Diff line change
@@ -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;
}

}
45 changes: 45 additions & 0 deletions src/test/fixture/org/kt3k/bankaccount/TransferContext.java
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,21 @@ 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

task.env = [TRAVIS: 'true', TRAVIS_JOB_ID: '123']

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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<SourceReport> 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()
}

}

0 comments on commit 4c6790b

Please sign in to comment.