/*
* Copyright 2011 Henry Coles and Stefan Penndorf
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pitest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import org.apache.commons.io.FileUtils;
import org.apache.maven.it.VerificationException;
import org.apache.maven.it.Verifier;
import org.apache.maven.it.util.ResourceExtractor;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.rules.TestName;
import org.pitest.support.DirectoriesOnlyWalker;
import org.pitest.testapi.execute.Pitest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Stefan Penndorf <stefan.penndorf@gmail.com>
*/
public class PitMojoIT {
private static Logger LOGGER = LoggerFactory.getLogger(PitMojoIT.class);
private static String VERSION = getVersion();
@Rule
public TestName testName = new TestName();
@Rule
public TemporaryFolder testFolder = new TemporaryFolder();
private Verifier verifier;
private long startTime;
@Before
public void beforeEachTest() {
LOGGER.info("running test '{}'", testName.getMethodName());
startTime = System.currentTimeMillis();
}
@After
public void afterEachTest() {
LOGGER.info("duration of test '{}' {}ms", testName.getMethodName(),
System.currentTimeMillis() - startTime);
}
@Test
public void shouldSetUserDirToArtefactWorkingDirectory() throws Exception {
prepare("/pit-33-setUserDir");
verifier.executeGoal("org.pitest:pitest-maven:mutationCoverage");
}
@Test(timeout=60000)
public void shouldNotHangWhenLargeAmountsOfConsoleOutput() throws Exception {
File testDir = prepare("/pit-process-hang");
verifier.executeGoal("test");
verifier.executeGoal("org.pitest:pitest-maven:mutationCoverage");
// checkout output looks sane, but main point is that test completed
assertThat(readResults(testDir))
.contains(
"<sourceFile>SomeCode.java</sourceFile>");
}
@Test
public void shouldProduceConsistantCoverageData() throws Exception {
File testDir = prepare("/pit-deterministic-coverage");
verifier.executeGoal("test");
verifier.executeGoal("org.pitest:pitest-maven:mutationCoverage");
String firstRun = readCoverage(testDir);
verifier.executeGoal("org.pitest:pitest-maven:mutationCoverage");
String secondRun = readCoverage(testDir);
assertEquals(firstRun, secondRun);
}
@Test
public void shouldWorkWithTestNG() throws Exception {
File testDir = prepare("/pit-testng");
verifier.executeGoal("test");
verifier.executeGoal("org.pitest:pitest-maven:mutationCoverage");
String actual = readResults(testDir);
assertThat(actual)
.contains(
"<mutation detected='true' status='KILLED'><sourceFile>Covered.java</sourceFile>");
assertThat(actual)
.contains(
"<mutation detected='false' status='NO_COVERAGE'><sourceFile>Covered.java</sourceFile>");
assertThat(actual).doesNotContain("status='RUN_ERROR'");
}
@Test
public void shouldWorkWithTestNGAndJMockit() throws Exception {
File testDir = prepare("/pit-testng-jmockit");
verifier.executeGoal("test");
verifier.executeGoal("org.pitest:pitest-maven:mutationCoverage");
String actual = readResults(testDir);
assertThat(actual)
.contains(
"<mutation detected='true' status='KILLED'><sourceFile>Covered.java</sourceFile>");
assertThat(actual)
.contains(
"<mutation detected='false' status='NO_COVERAGE'><sourceFile>Covered.java</sourceFile>");
assertThat(actual).doesNotContain("status='RUN_ERROR'");
}
@Test
public void shouldExcludeSpecifiedJUnitCategories() throws Exception {
File testDir = prepare("/pit-junit-categories");
verifier.executeGoal("test");
verifier.executeGoal("org.pitest:pitest-maven:mutationCoverage");
String actual = readResults(testDir);
String coverage = readCoverage(testDir);
assertThat(coverage).doesNotContain("NotCovered");
assertThat(coverage).contains("Covered");
assertThat(actual)
.contains(
"<mutation detected='false' status='NO_COVERAGE'><sourceFile>NotCovered.java</sourceFile>");
assertThat(actual)
.doesNotContain(
"<mutation detected='true' status='KILLED'><sourceFile>NotCovered.java</sourceFile>");
assertThat(actual)
.contains(
"<mutation detected='true' status='KILLED'><sourceFile>Covered.java</sourceFile>");
}
@Test
//@Ignore("test is flakey, possibly due to real non deterministic issue with powermock")
public void shouldWorkWithPowerMock() throws Exception {
File testDir = prepare("/pit-powermock");
verifier.addCliOption("-DtimeoutConstant=10000");
verifier.executeGoal("test");
verifier.executeGoal("org.pitest:pitest-maven:mutationCoverage");
String actual = readResults(testDir);
assertThat(actual)
.contains(
"<mutation detected='true' status='KILLED'><sourceFile>PowerMockAgentCallFoo.java</sourceFile>");
assertThat(actual)
.contains(
"<mutation detected='true' status='KILLED'><sourceFile>PowerMockCallsOwnMethod.java</sourceFile>");
assertThat(actual)
.contains(
"<mutation detected='true' status='KILLED'><sourceFile>PowerMockCallFoo.java</sourceFile>");
assertThat(actual).doesNotContain("status='RUN_ERROR'");
assertThat(actual).doesNotContain("<mutation detected='false' status='NO_COVERAGE'><sourceFile>PowerMockCallsOwnMethod.java</sourceFile><mutatedClass>com.example.PowerMockCallsOwnMethod</mutatedClass><mutatedMethod>branchedCode</mutatedMethod>");
}
@Test
public void shouldCorrectlyTargetTestsWhenMultipleBlocksIncludeALine()
throws Exception {
File testDir = prepare("/pit-158-coverage");
verifier.executeGoal("test");
verifier.executeGoal("org.pitest:pitest-maven:mutationCoverage");
String actual = readResults(testDir);
assertThat(actual)
.contains(
"<mutation detected='true' status='KILLED'><sourceFile>MyRequest.java</sourceFile>");
}
/*
* Verifies that configuring report generation to be skipped does actually
* prevent the site report from being generated.
*/
@Test
public void shouldSkipSiteReportGeneration() throws Exception {
File testDir = prepareSiteTest("/pit-site-skip");
File siteParentDir = buildFilePath(testDir, "target", "site");
verifier.executeGoal("site");
assertThat(siteParentDir).exists();
assertThat(buildFilePath(siteParentDir, "pit-reports")).doesNotExist();
assertThat(buildFilePath(siteParentDir, "index.html")).exists();
assertThat(buildFilePath(siteParentDir, "project-reports.html"))
.doesNotExist();
}
/*
* Verifies that running PIT with timestampedReports set to false will
* correctly copy the HTML report to the site reports directory.
*/
@Test
public void shouldGenerateSiteReportWithNonTimestampedHtmlReport()
throws Exception {
File testDir = prepareSiteTest("/pit-site-non-timestamped");
verifier.executeGoal("site");
verifyPitReportTest(testDir);
}
/*
* Verifies that running PIT with timestampedReports set to true will copy the
* contents of the latest timestamped report
*/
@Test
public void shouldGenerateSiteReportWithSingleTimestampedHtmlReport()
throws Exception {
File testDir = prepareSiteTest("/pit-site-timestamped", "201505212116");
verifier.executeGoal("site");
verifyPitReportTest(testDir);
}
/*
* Verifies that, when multiple timestamped PIT reports have been generated,
* only the latest report is copied to the site reports directory. This test
* sets the earlier directory (201503292032) as the last modified. This tests
* to make sure the last modified date is used instead of just using the
* folder name.
*/
@Test
public void shouldCopyLatestTimestampedReportWhenMultipleTimestampedReportsExist()
throws Exception {
File testDir = prepareSiteTest("/pit-site-multiple-timestamped",
"201503292032");
verifier.executeGoal("site");
verifyPitReportTest(testDir);
}
/*
* Verifies that in the case where pit has generated reports with both
* timestampedReports=true and timestampedReports=false, the latest report run
* is copied and no timestamped report subdirectories are copied
*/
@Test
public void shouldCopyLatestTimestampedOrNonTimestampedReportWhenBothExist()
throws Exception {
File testDir = prepareSiteTest("/pit-site-combined", "");
verifier.executeGoal("site");
verifyPitReportTest(testDir);
}
/*
* Verifies that the build fails when running the report goal without first
* running the mutationCoverage goal
*/
@Test
public void shouldFailIfNoPITReportAvailable() throws Exception {
prepare("/pit-site-reportonly");
try {
verifier.executeGoal("site");
fail("should fail");
} catch (VerificationException e) {
assertThat(e.getMessage())
.containsSequence(
"[ERROR] Failed to execute goal org.apache.maven.plugins:maven-site-plugin:",
":site (default-site) on project pit-site-reportonly: Execution default-site of goal org.apache.maven.plugins:maven-site-plugin:",
":site failed: could not find reports directory",
"pit-site-reportonly/target/pit-reports");
}
}
/*
* verifies that overriding defaults has the expected results
*/
@Test
public void shouldCorrectlyHandleOverrides() throws Exception {
File testDir = prepareSiteTest("/pit-site-custom-config");
File targetDir = buildFilePath(testDir, "target");
File expectedSiteReportDir = buildFilePath(testDir, "target", "site",
"foobar");
FileUtils.moveDirectory(buildFilePath(targetDir, "pit-reports"),
buildFilePath(targetDir, "new-report-location"));
verifier.executeGoal("site");
String projectReportsHtmlContents = FileUtils
.readFileToString(buildFilePath(testDir, "target", "site",
"project-reports.html"));
assertTrue(
"did not find expected anchor tag to pit site report",
projectReportsHtmlContents
.contains("<a href=\"foobar/index.html\" title=\"my-test-pit-report-name\">my-test-pit-report-name</a>"));
assertTrue("expected site report directory [" + expectedSiteReportDir
+ "] does not exist but should exist", expectedSiteReportDir.exists());
assertFalse(
"expected default site report directory exists but should not exist since the report location parameter was overridden",
buildFilePath(testDir, "target", "site", "pit-reports").exists());
}
@Test
public void shouldReadExclusionsFromSurefireConfig() throws Exception {
File testDir = prepare("/pit-surefire-excludes");
verifier.addCliOption("-DskipTests");
verifier.executeGoal("test");
verifier.executeGoal("org.pitest:pitest-maven:mutationCoverage");
String actual = readResults(testDir);
assertThat(actual)
.contains(
"<mutation detected='false' status='NO_COVERAGE'><sourceFile>NotCovered.java</sourceFile>");
}
@Test
public void shouldWorkWithGWTMockito() throws Exception {
File testDir = prepare("/pit-183-gwtmockito");
verifier.executeGoal("test");
verifier.executeGoal("org.pitest:pitest-maven:mutationCoverage");
String actual = readResults(testDir);
assertThat(actual)
.contains(
"<mutation detected='true' status='KILLED'><sourceFile>MyWidget.java</sourceFile>");
assertThat(actual)
.contains(
"<mutation detected='false' status='SURVIVED'><sourceFile>MyWidget.java</sourceFile>");
assertThat(actual).doesNotContain("status='RUN_ERROR'");
}
@Test
public void shouldWorkWithYatspec() throws Exception {
File testDir = prepare("/pit-263-yatspec");
verifier.executeGoal("test");
verifier.executeGoal("org.pitest:pitest-maven:mutationCoverage");
String actual = readResults(testDir);
assertThat(actual)
.contains(
"<mutation detected='true' status='KILLED'><sourceFile>SomeClass.java</sourceFile>");
assertThat(actual).doesNotContain("status='NO_COVERAGE'");
assertThat(actual).doesNotContain("status='RUN_ERROR'");
}
private static String readResults(File testDir) throws IOException {
File mutationReport = new File(testDir.getAbsoluteFile() + File.separator
+ "target" + File.separator + "pit-reports" + File.separator
+ "mutations.xml");
return FileUtils.readFileToString(mutationReport);
}
private static String readCoverage(File testDir) throws IOException {
File coverage = new File(testDir.getAbsoluteFile() + File.separator
+ "target" + File.separator + "pit-reports" + File.separator
+ "linecoverage.xml");
return FileUtils.readFileToString(coverage);
}
private File prepare(String testPath) throws IOException,
VerificationException {
String path = ResourceExtractor.extractResourcePath(getClass(), testPath,
testFolder.getRoot(), true).getAbsolutePath();
verifier = new Verifier(path);
verifier.setAutoclean(false);
verifier.setDebug(true);
verifier.getCliOptions().add("-Dpit.version=" + VERSION);
verifier.getCliOptions().add(
"-Dthreads=" + (Runtime.getRuntime().availableProcessors()));
return new File(testFolder.getRoot().getAbsolutePath() + testPath);
}
private static String getVersion() {
String path = "/version.prop";
InputStream stream = Pitest.class.getResourceAsStream(path);
Properties props = new Properties();
try {
props.load(stream);
stream.close();
return (String) props.get("version");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Creates a new {@link File} object building off an existing {@link File}
* object and appending subfolders.
*
* For example, if this function is called with these arguments:
* <code>buildFile(new File("/foo/bar"), "subdir1", "subdir2", "file1.txt");</code>
* The returned {@link File} object would represent the path:
* /foo/bar/subdir1/subdir/file1.txt
*
* @param base
* {@link File} representing the starting location
* @param pathParts
* {@link String} varags containing the subfolders to append to the
* base, this argument should contain at least one value and none of
* its values should be blank or null.
*
* @return {@link File}
*/
private File buildFilePath(File base, String... pathParts) {
StringBuilder path = new StringBuilder(base.getAbsolutePath());
for (String part : pathParts) {
path.append(File.separator).append(part);
}
return new File(path.toString());
}
/**
* Sets up a test of the reporting mojo simulating multiple runs of mvn
* install (as in the case where timestampedReports is set to true). First
* calls {@link #prepareSiteTest(String)} then walks all directories starting
* at target/pit-reports setting their last modified time to 0 (epoch time).
* Finally, the directory specified in the {@code latestDir} parameter has its
* last modified time set to {@link System#currentTimeMillis()}.
*
* @param testPath
* {@link String} see {@link #prepareSiteTest(String)}
* @param latestDir
* {@link String} containing the subdirectory of target/pit-reports
* that should be set as the latest, pass an empty string to indicate
* the target/pit-reports directory should be the latest
*
* @return {@link File} representing the temporary folder that was set up for
* this execution of the test
* @throws Exception
*/
private File prepareSiteTest(String testPath, String latestDir)
throws Exception {
File testDir = prepareSiteTest(testPath);
// location where the target directory would be if a mvn clean install was executed
File testTargetDir = this.buildFilePath(testDir, "target", "pit-reports");
DirectoriesOnlyWalker walker = new DirectoriesOnlyWalker();
for (File f : walker.locateDirectories(testTargetDir)) {
f.setLastModified(0L);
}
assertThat(
buildFilePath(testTargetDir, latestDir).setLastModified(
System.currentTimeMillis())).isTrue();
return testDir;
}
/**
* Sets up a test of the reporting mojo by simulating what a mvn clean install
* would do. After this function is executed, the maven site can be generated
* by executing the site goal.
*
* The provided {@code testPath} must have this folder structure set up
* underneath it: src/test/resources/pit-reports The contents of this
* directory will be moved to the target directory simulating a mvn clean
* install.
*
* @param testPath
* {@link String} location of the test to set up, this path is
* relative to <code>${basedir}/src/test/resources</code>
*
* @return {@link File} representing the temporary folder that was set up for
* this execution of the test
* @throws Exception
*/
private File prepareSiteTest(String testPath) throws Exception {
File tempTestExecutionDir = prepare(testPath);
File targetDir = this.buildFilePath(tempTestExecutionDir, "target",
"pit-reports");
FileUtils.copyDirectory(
buildFilePath(tempTestExecutionDir, "src", "test", "resources",
"pit-reports"), targetDir);
return tempTestExecutionDir;
}
private void verifyPitReportTest(File testDir) throws Exception {
File pitReportSiteDir = buildFilePath(testDir, "target", "site",
"pit-reports");
assertThat(pitReportSiteDir).exists();
assertThat(this.buildFilePath(pitReportSiteDir, "marker_expected.txt"))
.exists();
String projectReportsHtmlContents = FileUtils
.readFileToString(buildFilePath(testDir, "target", "site",
"project-reports.html"));
assertTrue(
"did not find expected anchor tag to pit site report",
projectReportsHtmlContents
.contains("<a href=\"pit-reports/index.html\" title=\"PIT Test Report\">PIT Test Report</a>"));
}
}