-
Notifications
You must be signed in to change notification settings - Fork 10
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
feat: use Local File Detector by default #146
base: 4.2.x
Are you sure you want to change the base?
Conversation
This comment was marked as resolved.
This comment was marked as resolved.
@matrei can you please review this one? |
TODO factory/serviceloader pattern ability as per grails#143 implementing it as @shared of ContainerSupport.groovy trait was wrong, and lead to a race condition as not picked up by matchesCurrentContainerConfiguration for obvious reason. also trying that was more complicated to implement, so not even commited to avoid people thinking i have no clue about advanced interlinked self-referencing trait patterns (i dont)
TODO: implement ServiceLoader Pattern as done in #143 to allow static overwriteability |
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 excellent! Now users don’t have to do anything special for uploads to work seamlessly in ContainerGebSpec
.
Looking forward to your addition of a ServiceLoader
implementation.
spock-container-test-app/src/integration-test/groovy/org/demo/spock/LocalUploadSpec.groovy
Outdated
Show resolved
Hide resolved
spock-container-test-app/src/integration-test/groovy/org/demo/spock/LocalUploadSpec.groovy
Outdated
Show resolved
Hide resolved
spock-container-test-app/src/integration-test/groovy/org/demo/spock/UselessUploadSpec.groovy
Outdated
Show resolved
Hide resolved
spock-container-test-app/src/integration-test/groovy/org/demo/spock/UselessUploadSpec.groovy
Outdated
Show resolved
Hide resolved
spock-container-test-app/src/integration-test/groovy/org/demo/spock/UselessUploadSpec.groovy
Outdated
Show resolved
Hide resolved
spock-container-test-app/src/integration-test/groovy/org/demo/spock/UselessUploadSpec.groovy
Outdated
Show resolved
Hide resolved
src/testFixtures/groovy/grails/plugin/geb/ContainerGebConfiguration.groovy
Outdated
Show resolved
Hide resolved
src/testFixtures/groovy/grails/plugin/geb/ContainerGebConfiguration.groovy
Outdated
Show resolved
Hide resolved
src/testFixtures/groovy/grails/plugin/geb/ContainerGebConfiguration.groovy
Outdated
Show resolved
Hide resolved
probably heading to do this and others on weekend
Since its my first time using this pattern, do you think it'd be ok if we create just one Loader Class for everything (which i cant implement here, as multiple PRs) or one Loader for each corresponding trait/interface (the latter of which i am going to write here) in the github code search projects i couldnt find much and if, it was always seperate small price utility loader class for one thing. whole idea of this question is i dont want to bloat the code with dozens of loaders in future |
Good point! Now that we have created our own Loader with override feature in #143, we could make that more generic (for the class) and specific for the methods. Then we can add loading of different types in that class as separate PR:s after #143 is merged. An alternative would be to create multiple loaders grouped in a package like |
intellij refactor + Spec needed manually
more minimalist serviceloader/spock/annotation README made a interface to allow future extensibility, i.e. maybe pass along current container info through an interface getter, similiar to how we pass errorInfo in other serviceloader? may have went overboard with this
may have went overboard, please correct me if simpler better or mistakes |
src/testFixtures/groovy/grails/plugin/geb/ContainerFileDetector.groovy
Outdated
Show resolved
Hide resolved
src/testFixtures/groovy/grails/plugin/geb/ContainerGebConfiguration.groovy
Show resolved
Hide resolved
src/testFixtures/groovy/grails/plugin/geb/DefaultContainerFileDetector.groovy
Outdated
Show resolved
Hide resolved
src/testFixtures/groovy/grails/plugin/geb/WebDriverContainerHolder.groovy
Outdated
Show resolved
Hide resolved
How about we create base service registry: package grails.plugin.geb
import groovy.transform.CompileStatic
import java.util.concurrent.ConcurrentHashMap
/**
* A service registry that loads and caches different service types using the Java
* {@link java.util.ServiceLoader}, while allowing overriding which instance to return.
* <p>
* This class provides thread-safe service loading and caching, supporting parallel test execution.
* It provides both automatic service discovery through {@link java.util.ServiceLoader}
* and explicit replacement of the returned service instance for customization.
* </p>
* <p>
* Usage example:
* <pre>
* MyService service = ServiceRegistry.getInstance(MyService, DefaultMyService)
* </pre>
* </p>
*
* @since 4.2
* @author Mattias Reichel
*/
@CompileStatic
class ServiceRegistry {
private static final ThreadLocal<ConcurrentHashMap<Class<?>, Object>> INSTANCES = ThreadLocal.withInitial {
new ConcurrentHashMap<Class<?>, Object>()
}
/**
* Returns the service instance of the given service type, loading it using
* {@link java.util.ServiceLoader} if not already loaded or an instance
* of the default implementation type if no service implementation is found
* by the {@link java.util.ServiceLoader}.
*
* If an instance has been set using {@link #setInstance(Class, Object)} or
* {@link #setInstance(Class, Class)}, that instance will be returned instead.
*
* @param serviceType The service type
* @param defaultType The service implementation type to use if no service
* implementation is found (Must have a zero-argument constructor)
* @return An instance of the service type
*/
static <T> T getInstance(Class<T> serviceType, Class<? extends T> defaultType) {
(T) INSTANCES.get().computeIfAbsent(serviceType) {
ServiceLoader.load(serviceType)
.findFirst()
.orElseGet { defaultType.getDeclaredConstructor().newInstance() }
}
}
/**
* Sets the instance to be returned for the given service type, bypassing instance
* loading from {@link java.util.ServiceLoader}.
* <p>
* Setting the instance to {@code null} will revert to loading the the service instance
* via the {@link java.util.ServiceLoader}.
* </p>
* @param serviceType The service type for which the instance should be set
* @param instance The instance to return for the given service type, or
* {@code null} for default service loading
*/
static <T> void setInstance(Class<T> serviceType, T instance) {
INSTANCES.get().put(serviceType, instance)
}
/**
* Sets the implementation type to return for the given service type, bypassing instance loading
* from {@link java.util.ServiceLoader}.
*
* @param serviceType The service type for which the instance should be set
* @param instanceType The type of the instance to return for the given service type.
* (Must have a zero-argument constructor).
*/
static <T> void setInstance(Class<T> serviceType, Class<? extends T> instanceType) {
setInstance(serviceType, instanceType.getDeclaredConstructor().newInstance())
}
} then we can delegate to it and do not have to manage any implementation details in the different concrete implementations: @CompileStatic
class ContainerFileDetectorServiceLoader {
/**
* Delegates to {@link ServiceRegistry#getInstance(Class, Class)}.
*/
static ContainerFileDetector getInstance() {
ServiceRegistry.getInstance(ContainerFileDetector, DefaultContainerFileDetector)
}
/**
* Delegates to {@link ServiceRegistry#setInstance(Class, Object)}.
*/
static void setInstance(ContainerFileDetector instance) {
ServiceRegistry.setInstance(ContainerFileDetector, instance)
}
/**
* Delegates to {@link ServiceRegistry#setInstance(Class, Class)}.
*/
static void setInstance(Class<? extends ContainerFileDetector> clazz) {
ServiceRegistry.setInstance(ContainerFileDetector, clazz)
}
} or perhaps even cleaner, call the if (currentConfiguration.fileDetector != NullContainerFileDetector) {
ServiceRegistry.setInstance(ContainerFileDetector, currentConfiguration.fileDetector)
}
ContainerFileDetector fileDetector = ServiceRegistry.getInstance(ContainerFileDetector, DefaultContainerFileDetector) What do you think? |
I knew about threadlocal, to be honest only through a random claude generation once, but yes with this concurrenthashmap it looks like solid code. Can you tell me why we need this threadlocal or concurrent thing though? If not ill study myself. But yes, totally can do with this |
As the |
Usually |
Actually, |
Long term, I was hoping to make it thread safe. I did this as a stop gap for now. I'd rather we go ahead and add threadlocal for future proofing. |
Totally, hell yeah |
Yeah, but I'm not sure this is going to work as I envisioned. If Spock is run in parallell mode, Also, currently I don't think overriding in void setupSpec() {
ContainerFileDetectorServiceLoader.setInstance(UselessContainerFileDetector)
} as the condition here will always be true if you don't use the annotation (so this will always run and set it to if (currentConfiguration.fileDetector != NullContainerFileDetector) {
ContainerFileDetectorServiceLoader.setInstance(currentConfiguration.fileDetector)
} and also ContainerFileDetector fileDetector = ContainerFileDetectorServiceLoader.getInstance()
((RemoteWebDriver) driver).setFileDetector(fileDetector) |
put to draft until i get to doing wished implementation of comments |
did not (yet) implement the third part of mentioned comment. still using ContainerFileDetectorServiceLoader only difference: put into .serviceloader package
…nt service loader implementations at all"
Yes, some added println with flushes confirm:
Until figure how to circumvent, will stay draft |
Resolves #128 (comment) and takes away my need for a custom utility function and reliance on
container.
as per my current predicament outlined in #134https://www.selenium.dev/documentation/webdriver/drivers/remote_webdriver/#uploads