Spike for expression language evaluation in display names#4293
Spike for expression language evaluation in display names#4293jean-andre-gauthier wants to merge 7 commits intojunit-team:mainfrom
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.
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.
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.
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 displayNameand aMap<String,Object> expressionContextMapand then allow people to pick between Mustache, SpEL, etc.
There was a problem hiding this comment.
That would require compiling with
javac's-parametersoption. We already check forparameter.isNamePresent()inParameterizedTestMethodContext.getParameterName(int), though, so we could populate theMapbased 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()).
sbrannen
left a comment
There was a problem hiding this comment.
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)
| testImplementation(testFixtures(projects.junitJupiterEngine)) | ||
| testImplementation(testFixtures(projects.junitPlatformLauncher)) | ||
| testImplementation(testFixtures(projects.junitPlatformReporting)) | ||
| implementation(libs.mustache) |
There was a problem hiding this comment.
| implementation(libs.mustache) | |
| testImplementation(libs.mustache) |
| 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.
| 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.
| 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.
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.
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
@ExpressionLanguageannotation, that takes aClass<ExpressionLanguageAdapter>as parameterExpressionLanguageAdapterinterface, that can be implemented to bind in a third-party expression languageExpressionLanguageMustacheTests, that demonstrate how@ExpressionLanguage / ExpressionLanguageAdaptercan 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
@APIannotations