Skip to content

Commit

Permalink
Retry Spock methods with custom @Unroll template
Browse files Browse the repository at this point in the history
Signed-off-by: Pavlo Shevchenko <pshevchenko@gradle.com>
  • Loading branch information
pshevche committed Apr 18, 2024
1 parent efa0596 commit 885ca22
Showing 1 changed file with 79 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,19 @@
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.MethodVisitor;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static java.util.Objects.requireNonNull;
import static org.objectweb.asm.Opcodes.ASM7;

/**
Expand All @@ -42,7 +45,7 @@ final class SpockParameterClassVisitor extends TestsReader.Visitor<Map<String, L

private final Set<String> failedTestNames;
private final TestsReader testsReader;
private final SpockParameterMethodVisitor spockMethodVisitor = new SpockParameterMethodVisitor();
private final Map<String, SpockParameterMethodVisitor> spockMethodVisitorByMethodName = new HashMap<>();
private boolean isSpec;

public SpockParameterClassVisitor(Set<String> testMethodName, TestsReader testsReader) {
Expand All @@ -57,24 +60,37 @@ public Map<String, List<String>> getResult() {
}

Map<String, List<String>> map = new HashMap<>();
spockMethodVisitor.annotationVisitor.testMethodPatterns.forEach(
methodPattern -> {
// Replace params in the method name with .*
String methodPatternRegex = Arrays.stream(methodPattern.split(SPOCK_PARAM_PATTERN))
.map(Pattern::quote)
.collect(Collectors.joining(WILDCARD))
+ WILDCARD; // For when no params in name - [iterationNum] implicitly added to end

failedTestNames.forEach(failedTestName -> {
List<String> matches = map.computeIfAbsent(failedTestName, ignored -> new ArrayList<>());
if (methodPattern.equals(failedTestName) || failedTestName.matches(methodPatternRegex)) {
matches.add(methodPattern);
}
});
spockMethodVisitorByMethodName.values().stream()
.filter(SpockParameterMethodVisitor::isSpockTestMethod)
.forEach(spockMethodVisitor -> {
Optional<String> unrollTemplate = spockMethodVisitor.getUnrollTemplate();
if (unrollTemplate.isPresent()) {
// if failed tests match the unroll template, we rerun the declared test method
addMatchingMethodForFailedTests(map, unrollTemplate.get(), spockMethodVisitor.getTestMethodName());
} else {
// if failed tests match the declared test method name/template, we rerun the declared test method
addMatchingMethodForFailedTests(map, spockMethodVisitor.getTestMethodName(), spockMethodVisitor.getTestMethodName());
}
});

return map;
}

private void addMatchingMethodForFailedTests(Map<String, List<String>> matchingMethodsPerFailedTest, String methodPattern, String methodName) {
// Replace params in the method name with .*
String methodPatternRegex = Arrays.stream(methodPattern.split(SPOCK_PARAM_PATTERN))
.map(Pattern::quote)
.collect(Collectors.joining(WILDCARD))
+ WILDCARD; // For when no params in name - [iterationNum] implicitly added to end

failedTestNames.forEach(failedTestName -> {
List<String> matches = matchingMethodsPerFailedTest.computeIfAbsent(failedTestName, ignored -> new ArrayList<>());
if (methodPattern.equals(failedTestName) || failedTestName.matches(methodPatternRegex)) {
matches.add(methodName);
}
});
}

@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
Expand All @@ -89,12 +105,15 @@ public void visit(int version, int access, String name, String signature, String

@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
return isSpec ? spockMethodVisitor : null;
return isSpec ? spockMethodVisitorByMethodName.computeIfAbsent(name, __ -> new SpockParameterMethodVisitor()) : null;
}

private static final class SpockParameterMethodVisitor extends MethodVisitor {

private final SpockFeatureMetadataAnnotationVisitor annotationVisitor = new SpockFeatureMetadataAnnotationVisitor();
@Nullable
private SpockFeatureMetadataAnnotationVisitor featureMethodAnnotationVisitor;
@Nullable
private SpockUnrollAnnotationVisitor unrollAnnotationVisitor;

public SpockParameterMethodVisitor() {
super(ASM7);
Expand All @@ -103,11 +122,28 @@ public SpockParameterMethodVisitor() {
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
if (descriptor.contains("org/spockframework/runtime/model/FeatureMetadata")) {
return annotationVisitor;
featureMethodAnnotationVisitor = new SpockFeatureMetadataAnnotationVisitor();
return featureMethodAnnotationVisitor;
}
if (descriptor.contains("spock/lang/Unroll")) {
unrollAnnotationVisitor = new SpockUnrollAnnotationVisitor();
return unrollAnnotationVisitor;
}
return null;
}

public boolean isSpockTestMethod() {
return featureMethodAnnotationVisitor != null;
}

public String getTestMethodName() {
return requireNonNull(requireNonNull(featureMethodAnnotationVisitor).testMethodName);
}

public Optional<String> getUnrollTemplate() {
return Optional.ofNullable(unrollAnnotationVisitor).map(visitor -> visitor.unrollTemplate);
}

/**
* Looking for signatures like:
* org/spockframework/runtime/model/FeatureMetadata;(
Expand All @@ -120,7 +156,7 @@ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
*/
private static final class SpockFeatureMetadataAnnotationVisitor extends AnnotationVisitor {

private final List<String> testMethodPatterns = new ArrayList<>();
private String testMethodName;

public SpockFeatureMetadataAnnotationVisitor() {
super(ASM7);
Expand All @@ -129,7 +165,30 @@ public SpockFeatureMetadataAnnotationVisitor() {
@Override
public void visit(String name, Object value) {
if ("name".equals(name)) {
testMethodPatterns.add((String) value);
testMethodName = (String) value;
}
}

}

/**
* Looking for signatures like:
* spock/lang/Unroll;(
* value="test for #a",
* )
*/
private static final class SpockUnrollAnnotationVisitor extends AnnotationVisitor {

private String unrollTemplate;

public SpockUnrollAnnotationVisitor() {
super(ASM7);
}

@Override
public void visit(String name, Object value) {
if ("value".equals(name)) {
unrollTemplate = (String) value;
}
}

Expand Down

0 comments on commit 885ca22

Please sign in to comment.