Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SUREFIRE-2152] Include name of JUnit5 templated-tests in test description #615

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -251,8 +255,14 @@ private SimpleReportEntry createReportEntry( TestIdentifier testIdentifier,
{
methodText = null;
}
StackTraceWriter stw =
testExecutionResult == null ? null : toStackTraceWriter( className, methodName, testExecutionResult );
String methodNameForSTW = methodName;
if ( methodNameForSTW != null && methodNameForSTW.contains( ")[" ) )
{
// don't pass suffix of templated-tests ("[1] ...") on to STW, as it's not part of the method signature
methodNameForSTW = methodNameForSTW.substring( 0, methodNameForSTW.indexOf( ")[" ) + 1 );
}
StackTraceWriter stw = testExecutionResult == null ? null
: toStackTraceWriter( className, methodNameForSTW, testExecutionResult );
return new SimpleReportEntry( runMode, classMethodIndexer.indexClassMethod( className, methodName ), className,
classText, methodName, methodText, stw, elapsedTime, reason, systemProperties );
}
Expand Down Expand Up @@ -362,6 +372,32 @@ 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)
// => Include the display-name of the actual invocation as well in the methodDesc of
// each invocation (also according to the 'name=' values of such tests), to have
// more context of what failed also e.g. from output of console reporter
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 + "]";
String displayForDesc = display;
if ( displayForDesc.startsWith( invocationIdStr ) )
{
// a bit hacky, try to prevent including ID multiple times in most default cases
// "foo()[1][1] abc def" -> "foo()[1] abc def"
displayForDesc = displayForDesc.substring( invocationIdStr.length() );
}
methodDesc = methodSignature + invocationIdStr + displayForDesc;
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 +426,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
Expand Up @@ -640,27 +640,27 @@ public void rerunParameterized()

assertEquals( TestClass7.class.getName(), reportEntries.get( 0 ).getSourceName() );
assertNull( reportEntries.get( 0 ).getSourceText() );
assertEquals( "testParameterizedTestCases(String, boolean)[1]", reportEntries.get( 0 ).getName() );
assertEquals( "testParameterizedTestCases(String, boolean)[1] Always pass, true",
reportEntries.get( 0 ).getNameText() );
reportEntries.get( 0 ).getName() );
assertEquals( null, reportEntries.get( 0 ).getNameText() );

assertEquals( TestClass7.class.getName(), reportEntries.get( 1 ).getSourceName() );
assertNull( reportEntries.get( 1 ).getSourceText() );
assertEquals( "testParameterizedTestCases(String, boolean)[2]", reportEntries.get( 1 ).getName() );
assertEquals( "testParameterizedTestCases(String, boolean)[2] Always fail, false",
reportEntries.get( 1 ).getNameText() );
reportEntries.get( 1 ).getName() );
assertEquals( null, reportEntries.get( 1 ).getNameText() );

assertEquals( TestClass7.class.getName(), reportEntries.get( 2 ).getSourceName() );
assertNull( reportEntries.get( 2 ).getSourceText() );
assertEquals( "testParameterizedTestCases(String, boolean)[2]", reportEntries.get( 2 ).getName() );
assertEquals( "testParameterizedTestCases(String, boolean)[2] Always fail, false",
reportEntries.get( 2 ).getNameText() );
reportEntries.get( 2 ).getName() );
assertEquals( null, reportEntries.get( 2 ).getNameText() );

assertEquals( TestClass7.class.getName(), reportEntries.get( 3 ).getSourceName() );
assertNull( reportEntries.get( 3 ).getSourceText() );
assertEquals( "testParameterizedTestCases(String, boolean)[2]", reportEntries.get( 3 ).getName() );
assertEquals( "testParameterizedTestCases(String, boolean)[2] Always fail, false",
reportEntries.get( 3 ).getNameText() );
reportEntries.get( 3 ).getName() );
assertEquals( null, reportEntries.get( 3 ).getNameText() );

TestExecutionSummary summary = executionListener.summaries.get( 0 );
assertEquals( 2, summary.getTestsFoundCount() );
Expand Down