Skip to content

Commit

Permalink
[SUREFIRE-2152] Handle JUnit5 templated-tests separately to set name/…
Browse files Browse the repository at this point in the history
…displayName

Ensures that all JUnit5 templated tests have a unique name that includes
their invocation ID (index)

This change also covers an issue of SUREFIRE-2087 where certain tests
(namely, tests defined through a @testtemplate) still cause mixups
of test invocations in relation with rerunFailingTestsCount, which
can be mixed up by surefire if their name isn't unique for each distinct
invocation.

For an example description of this issue, see also:
https://issues.apache.org/jira/browse/SUREFIRE-2087#comment-17690951
  • Loading branch information
s-rwe committed Feb 21, 2023
1 parent 208eae2 commit a87457b
Showing 1 changed file with 54 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import org.apache.maven.surefire.api.report.OutputReportEntry;
Expand Down Expand Up @@ -60,6 +62,8 @@
final class RunListenerAdapter
implements TestExecutionListener, TestOutputReceiver<OutputReportEntry>, RunModeSetter
{
private static final Pattern COMMA_PATTERN = Pattern.compile( "," );

private final ClassMethodIndexer classMethodIndexer = new ClassMethodIndexer();
private final ConcurrentMap<TestIdentifier, Long> testStartTime = new ConcurrentHashMap<>();
private final ConcurrentMap<TestIdentifier, TestExecutionResult> failures = new ConcurrentHashMap<>();
Expand Down Expand Up @@ -362,6 +366,23 @@ private String[] toClassMethodName( TestIdentifier testIdentifier )
// param || m()[1] | [1] <param>
// param+displ || m()[1] | displ

// Override resulting methodDesc/methodDisp values again, for invocations of
// JUnit5 templated-tests (such as @ParameterizedTest/@RepeatedTest)
Integer templatedTestInvocationId = extractTemplatedInvocationId( testIdentifier );
if ( templatedTestInvocationId != null )
{
String simpleClassNames = COMMA_PATTERN.splitAsStream( methodSource.getMethodParameterTypes() )
.map( s -> s.substring( 1 + s.lastIndexOf( '.' ) ).trim() )
.collect( joining( ", " ) );

String methodSignature = methodName + '(' + simpleClassNames + ')';

String invocationIdStr = "[" + templatedTestInvocationId + "]";

methodDesc = methodSignature + invocationIdStr;
methodDisp = parentDisplay + display;
}

return new String[] {source[0], source[1], methodDesc, methodDisp};
}
else if ( testSource.filter( ClassSource.class::isInstance ).isPresent() )
Expand Down Expand Up @@ -390,6 +411,39 @@ else if ( testSource.filter( ClassSource.class::isInstance ).isPresent() )
}
}

private static final Pattern TEST_TEMPLATE_INVOCATION_MATCHER =
Pattern.compile( "\\[test-template-invocation:#([1-9][0-9]*)]" );

/**
* If the given test-id defines an invocation of a templated-test (such as a specific
* instance of a @ParameterizedTest or @RepeatedTest), returns the invocation-id of
* that instance (1, 2, ...)
*
* <p>Returns null if the given test-id doesn't seem to be a templated-test invocation,
* or if no invocation-id could be extracted.
*/
private Integer extractTemplatedInvocationId( TestIdentifier testId )
{
/*
Note: with JUnit 5.8+, we could make this nicer using testId.getUniqueIdObject()
# Segment lastSegment = testId.getUniqueIdObject().getLastSegment();
# if (lastSegment.getType().equals(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE)) {
# String invocationIdStr = lastSegment.getValue(); // #1, #2, ...
# if (invocationIdStr.startsWith("#")) { // assuming always true
# return Integer.valueOf(invocationIdStr.substring(1));
# }
# }
*/
Matcher m = TEST_TEMPLATE_INVOCATION_MATCHER.matcher( testId.getUniqueId() );
if ( m.find() )
{
String group = m.group( 1 );
return Integer.valueOf( group );
}
return null;
}

/**
* @return Map of tests that failed.
*/
Expand Down

0 comments on commit a87457b

Please sign in to comment.