From a87457bb4ae077d96bea21de82a6bdc4e8bebed7 Mon Sep 17 00:00:00 2001 From: Ralph Weires Date: Tue, 21 Feb 2023 09:48:35 +0100 Subject: [PATCH] [SUREFIRE-2152] Handle JUnit5 templated-tests separately to set name/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 --- .../junitplatform/RunListenerAdapter.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/RunListenerAdapter.java b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/RunListenerAdapter.java index cafc934d05..e6626ff08b 100644 --- a/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/RunListenerAdapter.java +++ b/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/RunListenerAdapter.java @@ -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; @@ -60,6 +62,8 @@ final class RunListenerAdapter implements TestExecutionListener, TestOutputReceiver, RunModeSetter { + private static final Pattern COMMA_PATTERN = Pattern.compile( "," ); + private final ClassMethodIndexer classMethodIndexer = new ClassMethodIndexer(); private final ConcurrentMap testStartTime = new ConcurrentHashMap<>(); private final ConcurrentMap failures = new ConcurrentHashMap<>(); @@ -362,6 +366,23 @@ private String[] toClassMethodName( TestIdentifier testIdentifier ) // param || m()[1] | [1] // 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() ) @@ -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, ...) + * + *

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. */