-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Spike for expression language evaluation in display names #4293
base: main
Are you sure you want to change the base?
Spike for expression language evaluation in display names #4293
Conversation
…TestMethodContext
…stExtensionTests to TestExtensionContext
…erFormatter so that a custom expression language formatter can be plugged in
…ameFormatter, pull ArgumentsContext up to separate class
|
||
@Override | ||
public void append(ArgumentsContext argumentsContext, StringBuffer result) { | ||
expressionLanguageAdapter.format(argumentsContext.arguments.get()[0], result); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not correct, but I'm unsure how we should name multiple arguments, so that they can be accessed by the EL?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should provide a Map<String, Object>
using the method parameter names as keys. That would require compiling with javac
's -parameters
option. We already check for parameter.isNamePresent()
in ParameterizedTestMethodContext.getParameterName(int)
, though, so we could populate the Map
based on that and fail (?) if no parameter names are present?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding the Map<String, Object>
, that's also what I suggested in #1154 (comment).
In summary, I believe it would be better to design a new extension API that receives the
String displayName
and aMap<String,Object> expressionContextMap
and then allow people to pick between Mustache, SpEL, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would require compiling with
javac
's-parameters
option. We already check forparameter.isNamePresent()
inParameterizedTestMethodContext.getParameterName(int)
, though, so we could populate theMap
based on that and fail (?) if no parameter names are present?
I don't think I would throw an exception. Rather, I would document the behavior, pointing out that source code parameter names are available when compiled with -parameters
and that otherwise the arguments are available via arg0
, arg1
, etc. (i.e., the default parameter names returned by Parameter.getName()
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've only taken a quick glance, but for starters...
This new feature must be applicable to JUnit Jupiter in general, not just for parameterized tests.
So the APIs need to reside in the junit-jupiter-api
module.
Please revise your work to take that into account.
See also: #1154 (comment)
@@ -25,6 +25,7 @@ dependencies { | |||
testImplementation(testFixtures(projects.junitJupiterEngine)) | |||
testImplementation(testFixtures(projects.junitPlatformLauncher)) | |||
testImplementation(testFixtures(projects.junitPlatformReporting)) | |||
implementation(libs.mustache) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
implementation(libs.mustache) | |
testImplementation(libs.mustache) |
@@ -55,6 +55,7 @@ mavenSurefirePlugin = { module = "org.apache.maven.plugins:maven-surefire-plugin | |||
memoryfilesystem = { module = "com.github.marschall:memoryfilesystem", version = "2.8.1" } | |||
mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" } | |||
mockito-junit-jupiter = { module = "org.mockito:mockito-junit-jupiter", version.ref = "mockito" } | |||
mustache = { module = "com.github.spullara.mustache.java:compiler", version = "0.9.10"} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mustache = { module = "com.github.spullara.mustache.java:compiler", version = "0.9.10"} | |
mustache = { module = "com.github.spullara.mustache.java:compiler", version = "0.9.10" } |
try { | ||
ExpressionLanguageAdapter adapterInstance = adapterClass.getDeclaredConstructor().newInstance(); | ||
adapterInstance.compile(segment); | ||
return adapterInstance; | ||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) { | ||
String message = "Failed to initialize expression language for parameterized test. " | ||
+ "See nested exception for further details."; | ||
throw new JUnitException(message, ex); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
try { | |
ExpressionLanguageAdapter adapterInstance = adapterClass.getDeclaredConstructor().newInstance(); | |
adapterInstance.compile(segment); | |
return adapterInstance; | |
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) { | |
String message = "Failed to initialize expression language for parameterized test. " | |
+ "See nested exception for further details."; | |
throw new JUnitException(message, ex); | |
} | |
ExpressionLanguageAdapter adapterInstance = ReflectionSupport.newInstance(adapterClass); | |
adapterInstance.compile(segment); | |
return adapterInstance; |
|
||
@Override | ||
public void append(ArgumentsContext argumentsContext, StringBuffer result) { | ||
expressionLanguageAdapter.format(argumentsContext.arguments.get()[0], result); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should provide a Map<String, Object>
using the method parameter names as keys. That would require compiling with javac
's -parameters
option. We already check for parameter.isNamePresent()
in ParameterizedTestMethodContext.getParameterName(int)
, though, so we could populate the Map
based on that and fail (?) if no parameter names are present?
void compile(String template); | ||
|
||
void format(Object scope, StringBuffer result); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should split this into two to allow (not necessarily in this PR) registering a global default expression language that would support instantiating the ExpressionLanguageAdapter
at most once and also provide a hook to clean up resources by extending AutoCloseable
.
void compile(String template); | |
void format(Object scope, StringBuffer result); | |
Expression compile(String template); |
interface Expression {
void evaluate(Map<String, Object> context, Appendable result);
}
@sbrannen Besides parameterized tests, what other use cases with "dynamic display names" did you have in mind? |
There were discussions in #1154, #2452, and other places over the years where people expressed an interest in general-purpose expression language support. I think at the very least that this should be usable with any And I fear that tying this to |
Hi both, thanks very much for your reviews! I'll be off on holidays for the next two weeks, but will work on implementing #1154 (comment) after that (once it has been discussed with the team) |
Here I like to remind about my opinion on the subject #4246 (comment) - as a way to not "limit our options in this area". I.e. not tying it to It would allow JUnit Jupiter to expose an API for display-name manipulation as part of There is a kind of implementation for it, because some level of this functionality is required by LazyParams. |
Overview
@ExpressionLanguage
annotation, that takes aClass<ExpressionLanguageAdapter>
as parameterExpressionLanguageAdapter
interface, that can be implemented to bind in a third-party expression languageExpressionLanguageMustacheTests
, that demonstrate how@ExpressionLanguage / ExpressionLanguageAdapter
can be usedI'm marking this as a draft PR, because it's not entirely clear yet how the API should look like. I'll address the items in the Definition of Done once we have more clarity on that.
I hereby agree to the terms of the JUnit Contributor License Agreement.
Definition of Done
@API
annotations