-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Introduce Converter
in junit-platform-commons
#4219
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
base: main
Are you sure you want to change the base?
Conversation
There is plenty of work to do 🙃 The current highlights:
Any feedback would be highly appreciated! |
c31ca83
to
b19cba8
Compare
b19cba8
to
a7a8aa3
Compare
3304ad5
to
c886b7a
Compare
junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConverter.java
Outdated
Show resolved
Hide resolved
c886b7a
to
7ea91b8
Compare
Thanks for the draft! 👍 The tests are failing due to:
That's because
|
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.
Looks very promising! 👍
...m-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionSupport.java
Outdated
Show resolved
Hide resolved
...m-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionSupport.java
Show resolved
Hide resolved
...m-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionSupport.java
Outdated
Show resolved
Hide resolved
...ns/src/main/java/org/junit/platform/commons/support/conversion/DefaultConversionService.java
Outdated
Show resolved
Hide resolved
|
||
import org.junit.platform.commons.support.conversion.TypedConversionService; | ||
|
||
// FIXME delete |
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 would make a good test case, though. We have existing tests that register services for tests using an extra class loader:
junit5/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java
Lines 353 to 366 in 16c6f72
private static void withTestServices(Runnable runnable) { | |
var current = Thread.currentThread().getContextClassLoader(); | |
var url = LauncherFactoryTests.class.getClassLoader().getResource("testservices/"); | |
try (var classLoader = new URLClassLoader(new URL[] { url }, current)) { | |
Thread.currentThread().setContextClassLoader(classLoader); | |
runnable.run(); | |
} | |
catch (IOException e) { | |
throw new UncheckedIOException(e); | |
} | |
finally { | |
Thread.currentThread().setContextClassLoader(current); | |
} | |
} |
We could generalize and move that method to a test utility class (e.g. in junit-jupiter-api/src/testFixtures
) so it can be reused here.
When you add that, you'll also have to add it to |
2e17c2b
to
0c2faa7
Compare
I've been lagging behind with this one but I should be able to spend time on it in the upcoming weekend. |
0c2faa7
to
098c972
Compare
098c972
to
8ed6eb6
Compare
...m-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionSupport.java
Show resolved
Hide resolved
...-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java
Outdated
Show resolved
Hide resolved
8ed6eb6
to
940d018
Compare
Would you like to include this in 5.13 too? I should have enough time in the upcoming days to finalize it. |
0a36152
to
e380b8f
Compare
e380b8f
to
45effd9
Compare
@scordio Sorry for the late reply! I was out for a few days. If you still have time to finish it in the next two weeks, we could still include it in 5.13.0-RC1. WDYT? |
Sounds good! |
Sorry, for chiming in so late in the game, but... Instead of And I'm also wondering if it should be parameterized. FYI: I'm taking inspiration from the conversion APIs in the Spring Framework, which have been evolved over the years. For example, I see our Thoughts? |
I'd be okay with I'm not sure about parameterizing it. Where we use it (for example, to dynamically resolve method parameters), we won't be able to benefit from the type arguments. It might be a bit nicer for One thing we should consider is using a parameter object (like Spring's |
Then let's change it to
Although we (the framework itself) might not benefit from generics at the moment, I would rather introduce them now than later discover that we actually need them ourselves to support additional (more dynamic) use cases. In other words, what downside would there be to supporting generics from the start?
Yes, as you know... I am a big fan of "parameter context objects" in APIs that need to stand the test of time. As I mentioned before, the conversion subsystem in Spring Framework has evolved organically over time, and most modern implementations actually implement for GenericConverter and In fact, there's even a dedicated I am of course not suggesting that we introduce mechanisms as complex as those in Spring; however, I do hope that we can design in the API in such a manner that we don't prevent ourselves from being able to evolve the APIs and feature set over time as necessary. And... supporting generics and something like a |
This comment was marked as outdated.
This comment was marked as outdated.
I've hidden my last comment as it's mostly outdated after @sbrannen's one, and
If you'd like to go this way, I'm happy to adjust my work in this direction! |
I agree with @sbrannen. Let's go with the generic version and the parameter object. |
I think Any better ideas? EDIT: or maybe it is? 🤔 looking at how the Spring Framework does it. |
I think it's OK for |
@sbrannen I'm still trying to get a proper background before jumping on the actual changes, so here's a dumb question 🙂 Specifically about parameterized types, is the |
To start with, I had thought a relatively simple interface should suffice for interface TypeDescriptor {
Class<?> getType();
} @sbrannen WDYT? |
My suggestion is not to close the door for Mostly to be ready for cases where |
We could do what Java did (e.g. for |
So far, I understood public static <T> T convert(Object source, Class<T> targetType, ClassLoader classLoader) should become: public static <T> T convert(Object source, TypeDescriptor targetType, ClassLoader classLoader) Therefore, I planned to define @marcphilipp did you have something else in mind? |
45effd9
to
a8d8356
Compare
ConversionService
in junit-platform-commons
Converter
in junit-platform-commons
a8d8356
to
d3675e2
Compare
After renaming to I propose to refactor the WDYT? |
That's pretty much the vision I had all along: to have single abstraction/API. So, yes, that sounds good to me. 👍 Though... the devil is in details. 😉 |
public Class<?> getType() { | ||
return type; | ||
} |
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.
For now, most of the code relies on getType()
, but I plan to check if dedicated APIs could better encapsulate some use cases (similar to getWrapperType
or isPrimitive
).
} | ||
return null; | ||
} | ||
return convert(source, TypeDescriptor.forType(targetType), getClassLoader(classLoader)); |
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 wonder if this should delegate to the other convert
method or should invoke DefaultConverter
directly, skipping the new service loader logic. For now, I went with the former.
* never {@code null} | ||
* @return {@code true} if the supplied source can be converted | ||
*/ | ||
boolean canConvert(Object source, TypeDescriptor targetType); |
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.
Initially, this was:
boolean canConvert(Object source, TypeDescriptor targetType); | |
boolean canConvert(Object source, TypeDescriptor targetType, ClassLoader classLoader); |
However, I couldn't see the benefit of the ClassLoader
parameter here, so I deleted it. Let me know if you see some use cases that could benefit from it.
Plus, I'm not entirely sure if this should rather be:
boolean canConvert(Object source, TypeDescriptor targetType); | |
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType); |
The way Converter
is currently integrated into ConversionSupport
makes it easy to provide the source instance, but I wonder if this will be flexible enough with a bit of foresight.
* type is a reference type | ||
* @throws ConversionException if an error occurs during the conversion | ||
*/ | ||
protected abstract T convert(S source) throws ConversionException; |
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.
Should this carry the target TypeDescriptor
too?
protected abstract T convert(S source) throws ConversionException; | |
protected abstract T convert(S source, TypeDescriptor targetType) throws ConversionException; |
* <p>This method will only be invoked in {@link #canConvertTo(Class)} | ||
* returned {@code true} for the same target type. | ||
*/ | ||
Object convert(String source, Class<?> targetType) throws Exception; |
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.
Being an internal API, I deleted this convert(String, Class<?>)
variant to reduce complexity. The downside is that the ClassLoader
parameter is mostly unused in the subclasses.
Overview
ConversionService
SPI #853I hereby agree to the terms of the JUnit Contributor License Agreement.
Definition of Done
@API
annotations