Skip to content

Commit

Permalink
New extension points
Browse files Browse the repository at this point in the history
Adds new extension points to allow post analysis modification of
coverage and mutation analysis results.

Extensions points have multiple potential uses, but first use case is
supporting the 'un-inlining' of inlined kotlin functions.

Change requires alteration of existing interfaces which may be
incompatible with some third party plugins.
  • Loading branch information
hcoles committed Jan 25, 2023
1 parent 5e0fd2d commit 76a7ef3
Show file tree
Hide file tree
Showing 37 changed files with 555 additions and 263 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ Read all about it at http://pitest.org

## Releases

## 1.10.5 (unreleased)
## 1.11.0 (unreleased)

* #1138 Do not mutate redundant fall through to default switch cases
* #1150 New extension points

Note that #1150 includes breaking interface changes which may require updates to third party plugins.

## 1.10.4

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.pitest.classpath.ClassPath;
import org.pitest.classpath.ClassPathRoot;
import org.pitest.classpath.CodeSource;
import org.pitest.classpath.DefaultCodeSource;
import org.pitest.classpath.DirectoryClassPathRoot;
import org.pitest.classpath.PathFilter;
import org.pitest.classpath.ProjectClassPaths;
Expand All @@ -30,7 +31,7 @@ class CodeSourceAggregator {
}

public CodeSource createCodeSource() {
return new CodeSource(createProjectClassPaths());
return new DefaultCodeSource(createProjectClassPaths());
}

private ProjectClassPaths createProjectClassPaths() {
Expand Down
74 changes: 10 additions & 64 deletions pitest-entry/src/main/java/org/pitest/classpath/CodeSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,89 +3,35 @@
import java.util.Collection;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.pitest.bytecode.analysis.ClassTree;
import org.pitest.classinfo.ClassByteArraySource;
import org.pitest.classinfo.ClassInfo;
import org.pitest.classinfo.ClassInfoSource;
import org.pitest.classinfo.ClassName;
import org.pitest.classinfo.NameToClassInfo;
import org.pitest.classinfo.Repository;
import org.pitest.classinfo.TestToClassMapper;
import org.pitest.functional.Streams;

/**
* Provides access to code and tests on the classpath
*/
public class CodeSource implements ClassInfoSource, ClassByteArraySource {
public interface CodeSource extends ClassInfoSource, ClassByteArraySource {

private final ProjectClassPaths classPath;
private final Repository classRepository;
Stream<ClassTree> codeTrees();

public CodeSource(final ProjectClassPaths classPath) {
this(classPath, new Repository(new ClassPathByteArraySource(
classPath.getClassPath())));
}
Set<ClassName> getCodeUnderTestNames();

CodeSource(final ProjectClassPaths classPath,
final Repository classRepository) {
this.classPath = classPath;
this.classRepository = classRepository;
}
Stream<ClassTree> testTrees();

public Stream<ClassTree> codeTrees() {
return this.classPath.code().stream()
.map(c -> this.getBytes(c.asJavaName()))
.filter(Optional::isPresent)
.map(maybe -> ClassTree.fromBytes(maybe.get()));
}
ClassPath getClassPath();

public Set<ClassName> getCodeUnderTestNames() {
return this.classPath.code().stream().collect(Collectors.toSet());
}
Optional<ClassName> findTestee(String className);

public Stream<ClassTree> testTrees() {
return this.classPath.test().stream()
.map(c -> this.getBytes(c.asJavaName()))
.filter(Optional::isPresent)
.map(maybe -> ClassTree.fromBytes(maybe.get()))
.filter(t -> !t.isAbstract());
}

public ClassPath getClassPath() {
return this.classPath.getClassPath();
}

public Optional<ClassName> findTestee(final String className) {
final TestToClassMapper mapper = new TestToClassMapper(this.classRepository);
return mapper.findTestee(className);
}

public Collection<ClassInfo> getClassInfo(final Collection<ClassName> classes) {
return classes.stream()
.flatMap(nameToClassInfo())
.collect(Collectors.toList());
}

public Optional<byte[]> fetchClassBytes(final ClassName clazz) {
return this.classRepository.querySource(clazz);
}
Optional<byte[]> fetchClassBytes(ClassName clazz);

@Override
public Optional<ClassInfo> fetchClass(final ClassName clazz) {
return this.classRepository.fetchClass(clazz);
}

private Function<ClassName, Stream<ClassInfo>> nameToClassInfo() {
return new NameToClassInfo(this.classRepository)
.andThen(Streams::fromOptional);
}
Optional<ClassInfo> fetchClass(ClassName clazz);
Collection<ClassInfo> getClassInfo(Collection<ClassName> classes);

@Override
public Optional<byte[]> getBytes(String clazz) {
return fetchClassBytes(ClassName.fromString(clazz));
}
Optional<byte[]> getBytes(String clazz);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.pitest.classpath;

import org.pitest.plugin.ToolClasspathPlugin;

public interface CodeSourceFactory extends ToolClasspathPlugin {
CodeSource createCodeSource(ProjectClassPaths classPath);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package org.pitest.classpath;

import org.pitest.bytecode.analysis.ClassTree;
import org.pitest.classinfo.ClassInfo;
import org.pitest.classinfo.ClassName;
import org.pitest.classinfo.NameToClassInfo;
import org.pitest.classinfo.Repository;
import org.pitest.classinfo.TestToClassMapper;
import org.pitest.functional.Streams;

import java.util.Collection;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class DefaultCodeSource implements CodeSource {
private final ProjectClassPaths classPath;
private final Repository classRepository;

public DefaultCodeSource(final ProjectClassPaths classPath) {
this(classPath, new Repository(new ClassPathByteArraySource(
classPath.getClassPath())));
}

DefaultCodeSource(final ProjectClassPaths classPath,
final Repository classRepository) {
this.classPath = classPath;
this.classRepository = classRepository;
}

public Stream<ClassTree> codeTrees() {
return this.classPath.code().stream()
.map(c -> this.getBytes(c.asJavaName()))
.filter(Optional::isPresent)
.map(maybe -> ClassTree.fromBytes(maybe.get()));
}

public Set<ClassName> getCodeUnderTestNames() {
return this.classPath.code().stream().collect(Collectors.toSet());
}

public Stream<ClassTree> testTrees() {
return this.classPath.test().stream()
.map(c -> this.getBytes(c.asJavaName()))
.filter(Optional::isPresent)
.map(maybe -> ClassTree.fromBytes(maybe.get()))
.filter(t -> !t.isAbstract());
}

public ClassPath getClassPath() {
return this.classPath.getClassPath();
}

public Optional<ClassName> findTestee(final String className) {
final TestToClassMapper mapper = new TestToClassMapper(this.classRepository);
return mapper.findTestee(className);
}

public Collection<ClassInfo> getClassInfo(final Collection<ClassName> classes) {
return classes.stream()
.flatMap(nameToClassInfo())
.collect(Collectors.toList());
}

public Optional<byte[]> fetchClassBytes(final ClassName clazz) {
return this.classRepository.querySource(clazz);
}

@Override
public Optional<ClassInfo> fetchClass(final ClassName clazz) {
return this.classRepository.fetchClass(clazz);
}

private Function<ClassName, Stream<ClassInfo>> nameToClassInfo() {
return new NameToClassInfo(this.classRepository)
.andThen(Streams::fromOptional);
}

@Override
public Optional<byte[]> getBytes(String clazz) {
return fetchClassBytes(ClassName.fromString(clazz));
}
}
21 changes: 21 additions & 0 deletions pitest-entry/src/main/java/org/pitest/coverage/ClassLines.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
import org.pitest.bytecode.analysis.ClassTree;
import org.pitest.classinfo.ClassName;

import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner;
import java.util.stream.Collectors;

public final class ClassLines {
private final ClassName name;
Expand All @@ -23,6 +26,16 @@ public ClassName name() {
return name;
}

public ClassLines relocate(ClassName name) {
return new ClassLines(name, codeLines);
}

public List<ClassLine> asList() {
return codeLines.stream()
.map(l -> new ClassLine(name, l))
.collect(Collectors.toList());
}

public int getNumberOfCodeLines() {
return this.codeLines.size();
}
Expand All @@ -47,4 +60,12 @@ public boolean equals(Object o) {
public int hashCode() {
return Objects.hash(name);
}

@Override
public String toString() {
return new StringJoiner(", ", ClassLines.class.getSimpleName() + "[", "]")
.add("name=" + name)
.add("codeLines=" + codeLines)
.toString();
}
}
33 changes: 4 additions & 29 deletions pitest-entry/src/main/java/org/pitest/coverage/CoverageData.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

package org.pitest.coverage;

import org.pitest.bytecode.analysis.ClassTree;
import org.pitest.classinfo.ClassInfo;
import org.pitest.classinfo.ClassName;
import org.pitest.classpath.CodeSource;
Expand Down Expand Up @@ -78,11 +77,6 @@ public Collection<TestInfo> getTestsForBlockLocation(BlockLocation location) {
return this.blockCoverage.getOrDefault(location, Collections.emptySet());
}

@Override
public Collection<TestInfo> getTestsForClassLine(final ClassLine classLine) {
return legacyClassCoverage.getTestsForClassLine(classLine);
}

public boolean allTestsGreen() {
return this.failingTestDescriptions.isEmpty();
}
Expand All @@ -96,13 +90,13 @@ public List<Description> getFailingTestDescriptions() {
}

@Override
public Optional<ClassLines> getCoveredLinesForClass(ClassName clazz) {
return legacyClassCoverage.getCoveredLinesForClass(clazz);
public ClassLines getCodeLinesForClass(ClassName clazz) {
return legacyClassCoverage.getCodeLinesForClass(clazz);
}

@Override
public int getNumberOfCoveredLines(final Collection<ClassName> mutatedClass) {
return legacyClassCoverage.getNumberOfCoveredLines(mutatedClass);
public Set<ClassLine> getCoveredLines(ClassName clazz) {
return legacyClassCoverage.getCoveredLines(clazz);
}

@Override
Expand Down Expand Up @@ -144,11 +138,6 @@ public Collection<ClassLines> getClassesForFile(final String sourceFile,
return legacyClassCoverage.getClassesForFile(sourceFile, packageName);
}

@Override
public CoverageSummary createSummary() {
return new CoverageSummary(numberOfLines(), coveredLines());
}

private BigInteger generateCoverageNumber(Collection<TestInfo> coverage) {
BigInteger coverageNumber = BigInteger.ZERO;
Set<ClassName> testClasses = coverage.stream()
Expand All @@ -162,20 +151,6 @@ private BigInteger generateCoverageNumber(Collection<TestInfo> coverage) {
return coverageNumber;
}

private Collection<ClassName> allClasses() {
return this.code.getCodeUnderTestNames();
}

private int numberOfLines() {
return this.code.codeTrees()
.map(ClassTree::numberOfCodeLines)
.reduce(0, Integer::sum);
}

private int coveredLines() {
return getNumberOfCoveredLines(allClasses());
}

private void checkForFailedTest(final CoverageResult cr) {
if (!cr.isGreenTest()) {
recordTestFailure(cr.getTestUnitDescription());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,4 @@ public interface CoverageDatabase extends ReportCoverage {

BigInteger getCoverageIdForClass(ClassName clazz);

CoverageSummary createSummary();

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.util.TreeSet;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* Line based coverage data, used by html report and the history system
Expand Down Expand Up @@ -41,29 +42,20 @@ public void loadBlockDataOnly(Collection<BlockLocation> coverageData) {
}

@Override
public Optional<ClassLines> getCoveredLinesForClass(final ClassName clazz) {
public ClassLines getCodeLinesForClass(final ClassName clazz) {
return code.fetchClassBytes(clazz)
.map(ClassTree::fromBytes)
.map(ClassLines::fromTree);
.map(ClassLines::fromTree)
.orElse(new ClassLines(clazz, Collections.emptySet()));
}

@Override
public int getNumberOfCoveredLines(Collection<ClassName> mutatedClass) {
return mutatedClass.stream()
.map(this::getLineCoverageForClassName)
.mapToInt(Map::size)
.sum();
}

@Override
public Collection<TestInfo> getTestsForClassLine(final ClassLine classLine) {
final Collection<TestInfo> result = getLineCoverageForClassName(
classLine.getClassName()).get(classLine);
if (result == null) {
return Collections.emptyList();
} else {
return result;
}
public Set<ClassLine> getCoveredLines(ClassName mutatedClass) {
return Stream.of(mutatedClass)
.flatMap(m -> getLineCoverageForClassName(m).entrySet().stream())
.filter(e -> !e.getValue().isEmpty())
.map( e -> e.getKey())
.collect(Collectors.toSet());
}

@Override
Expand Down
Loading

0 comments on commit 76a7ef3

Please sign in to comment.