Skip to content

Commit

Permalink
filter null stream mutations called only by flatmap
Browse files Browse the repository at this point in the history
  • Loading branch information
Henry Coles committed Aug 11, 2021
1 parent 0910d03 commit da553a6
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ public boolean isSynthetic() {
return (this.rawNode.access & Opcodes.ACC_SYNTHETIC) != 0;
}

public boolean isPrivate() {
return (this.rawNode.access & Opcodes.ACC_PRIVATE) != 0;
}

public boolean returns(ClassName clazz) {
return this.rawNode.desc.endsWith("L" + clazz.asInternalName() + ";");
}

public List<AnnotationNode> annotations() {
final List<AnnotationNode> annotaions = new ArrayList<>();
if (this.rawNode.invisibleAnnotations != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.pitest.mutationtest.build.intercept.equivalent;

import org.objectweb.asm.Opcodes;
import org.pitest.bytecode.analysis.ClassTree;
import org.pitest.bytecode.analysis.MethodTree;
import org.pitest.classinfo.ClassName;
import org.pitest.mutationtest.build.InterceptorType;
import org.pitest.mutationtest.build.MutationInterceptor;
import org.pitest.mutationtest.engine.Mutater;
import org.pitest.mutationtest.engine.MutationDetails;

import java.util.Collection;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class NullFlatMapFilter implements MutationInterceptor {
private ClassTree currentClass;

@Override
public InterceptorType type() {
return InterceptorType.FILTER;
}

@Override
public void begin(ClassTree clazz) {
currentClass = clazz;
}

@Override
public Collection<MutationDetails> intercept(Collection<MutationDetails> mutations, Mutater unused) {
return mutations.stream()
.filter(m -> !this.mutatesStreamEmpty(m))
.collect(Collectors.toList());
}

private boolean mutatesStreamEmpty(MutationDetails mutationDetails) {
MethodTree mutated = currentClass.method(mutationDetails.getId().getLocation()).get();
if (!mutated.isPrivate() || !mutated.returns(ClassName.fromClass(Stream.class))) {
return false;
}

if (mutated.instruction(mutationDetails.getInstructionIndex()).getOpcode() != Opcodes.ARETURN) {
return false;
}

return true;
}

@Override
public void end() {
currentClass = null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.pitest.mutationtest.build.intercept.equivalent;

import org.pitest.mutationtest.build.InterceptorParameters;
import org.pitest.mutationtest.build.MutationInterceptor;
import org.pitest.mutationtest.build.MutationInterceptorFactory;
import org.pitest.plugin.Feature;

public class NullFlatMapFilterFactory implements MutationInterceptorFactory {

@Override
public MutationInterceptor createInterceptor(InterceptorParameters params) {
return new NullFlatMapFilter();
}

@Override
public Feature provides() {
return null;
}

@Override
public String description() {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package org.pitest.mutationtest.build.intercept.equivalent;

import org.junit.Test;
import org.pitest.mutationtest.build.InterceptorType;
import org.pitest.mutationtest.build.MutationInterceptor;
import org.pitest.mutationtest.build.intercept.javafeatures.FilterTester;
import org.pitest.mutationtest.engine.gregor.mutators.NullReturnValsMutator;
import org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator;

import java.util.List;
import java.util.stream.Stream;

import static org.assertj.core.api.Assertions.assertThat;

public class NullFlatmapTest {

MutationInterceptor testee = new NullFlatMapFilterFactory().createInterceptor(null);

FilterTester verifier = new FilterTester("", this.testee, NullReturnValsMutator.NULL_RETURN_VALUES,
VoidMethodCallMutator.VOID_METHOD_CALL_MUTATOR);

@Test
public void declaresTypeAsFilter() {
assertThat(this.testee.type()).isEqualTo(InterceptorType.FILTER);
}

@Test
public void filtersNullReturnMutantWhenMethodUsedOnlyInFlatMap() {
verifier.assertFiltersNMutationFromClass(111, HasPrivateStreamMethodUsedOnlyInSingleFlatMap.class);
}

@Test
public void doesNotFilterNullReturnsInPublicStreamMethods() {
verifier.assertFiltersNMutationFromClass(0, HasPublicStreamMethodUsedOnlyInSingleFlatMap.class);
}

@Test
public void doesNotFilterOtherMutantsInNullReturnsStreamMethods() {
verifier.assertFiltersNMutationFromClass(1, HasPrivateStreamMethodUsedOnlyInSingleFlatMapThatHasOtherMutableCode.class);
}

@Test
public void doesNotFilterNullReturnsWhenOriginalNotEmptyStream() {
verifier.assertFiltersNMutationFromClass(0, HasPrivateStreamMethodThatDoesNotReturnEmpty.class);
}

}

class HasPrivateStreamMethodUsedOnlyInSingleFlatMap {
public Stream<String> makesCall(List<String> l) {
return l.stream()
.flatMap(this::aStream);

}

private Stream<String> aStream(String l) {
return Stream.empty();
}
}

class HasPrivateStreamMethodUsedOnlyInSingleFlatMapThatHasOtherMutableCode {
public Stream<String> makesCall(List<String> l) {
return l.stream()
.flatMap(this::aStream);

}

private Stream<String> aStream(String l) {
System.out.println("Keep mutating me");
return Stream.empty();
}
}

class HasPrivateStreamMethodThatDoesNotReturnEmpty {
public Stream<String> makesCall(List<String> l) {
return l.stream()
.flatMap(this::aStream);

}

private Stream<String> aStream(String l) {
return Stream.of("");
}
}


class HasPublicStreamMethodUsedOnlyInSingleFlatMap {
public Stream<String> makesCall(List<String> l) {
return l.stream()
.flatMap(this::aStream);

}

public Stream<String> aStream(String l) {
return Stream.empty();
}
}

0 comments on commit da553a6

Please sign in to comment.