-
Notifications
You must be signed in to change notification settings - Fork 321
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
Elegant way to define additional or overriding Guice bindings in DSL tests #3001
Comments
Is this about unit tests or plug-in tests |
Just a quick answer, more on that tomorrow. @cdietrich that "trick" in the generated InjectorProvider is not required (but it's harmless) when running JUnit test from Eclipse where the classpath and the classloader are flat. It's required when you run those tests as plug-in tests, NOT necessarily requiring the UI. Since Tycho surefire always runs tests in OSGI, it's like running a plug-in test. In general, to reproduce the problems that the "trick" fixes is enough to run the test as a plug-in test, even without the UI. @HannesWell in general I like your proposal because that "trick" added by me was a first step but I always knew that a custom injector provider requires the ceremony. I'd like to think about a possible solution in more depth, because I think we could also take the chance to improve also the "trick" of the classloader further. |
@szarnekow wdyt |
Initial thoughts: I'm all in favor of making the generated injector provider more extensible. public class MyDSLInjectorProvider implements IInjectorProvider, IRegistryConfigurator {
protected Injector internalCreateInjector() {
return new MyDSLStandaloneSetup() {
@Override
public Injector createInjector() {
return Guice.createInjector(createModule() );
}
}.createInjectorAndDoEMFRegistration();
}
protected Module createModule() {
... as before
}
} public class MyDSLExtendedInjectorProvider extends MyDSLInjectorProvider {
@Override
protected Module createModule() {
return Modules2.mixin(super.createModule(), binder -> {
binder.bind(SomeInterface.class).to(TestImplementationOfSomeInterface.class);
});
}
} We could even go one step further and allow |
I'm afraid I'm a bit busy today, so I'm unable to fully explain my idea now. I'll try to give an hint: to fix the problem of the classloader to work in OSGI, it is enough to rely on the default implementation https://github.com/eclipse/xtext/blob/73bd1580bcea48d7ed27f990cadbc5ae4d308572/org.eclipse.xtext/src-gen/org/eclipse/xtext/AbstractXtextRuntimeModule.java#L60 , which is based on dynamic binding: if we generate another runtime module for testing purposes in the tests project, which only inherits from the runtime module of the main project, the classloader will be naturally picked correctly. I'm suggesting to use the generation gap pattern again. |
So, I remembered correctly: to make the classloading work it is enough to do like this in the generated injector provider: protected MyDslRuntimeModule createRuntimeModule() {
// make it work also with Maven/Tycho and OSGI
// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=493672
// allows for bindClassLoaderToInstance to get the class loader of this bundle
return new MyDslRuntimeModule() {
};
} I've just tested it in 4e695af I didn't make it like that in the first place because Sven suggested to be more explicit (https://bugs.eclipse.org/bugs/show_bug.cgi?id=493672#c1):
I think it's better to be "less explicit" nowadays ;) The "ceremony" mentioned by @HannesWell is not required, at least, there's no need to override There's still one problem though if we plan to provide the additional customization options: if you derive from the injector provider in another test bundle, based on the test bundle of a test language (which we do, e.g., in the tests of 4e695af), you still have to redefine the protected MyDslRuntimeModule createRuntimeModule() {
// make it work also with Maven/Tycho and OSGI
// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=493672
// allows for bindClassLoaderToInstance to get the class loader of this bundle
return new MyDslRuntimeModule() {
@Override
public ClassLoader bindClassLoaderToInstance() {
return MyDslInjectorProvider.this.getClass()
.getClassLoader();
}
};
} please let me stress it: I suggest to do MyDslInjectorProvider.this.getClass().getClassLoader(); instead of the current MyDslInjectorProvider.class.getClassLoader(); That way, the 4e695af could be further simplified (I've tested it locally by manually modifying the generated injector provider): public static class XImportSectionTestLangInjectorProviderCustom extends XImportSectionTestLangInjectorProvider {
// nothing else to do here
} In fact, the classloader would be taken from the class of This would make the suggested improvements in customizations even easier to use. @szarnekow what do you think? |
Yes, that would be totally fine for me as well and would be the approach with the least changes and yet greatest flexibility.
Yes I thought about that as well but discarded it at least for my use-case since I would have to adapt each test case to use the customized module. Nevertheless I think it would be useful as complementary change to allow local customization's for specific tests only. Then one could for example define a custom module for a test-class just within that class as static inner class. But to achieve that I think the generated
One really has to think about it sharply and twice, but it should work as expected. 👍🏽 If one creates an instance of a sub-class of the generated If you are fine with all the suggestions I can provide a PR to resolve this. |
For the test plugin of our custom DSL I want to define extra bindings for the Guice-Injector used to inject objects in the test.
Since the usual setup in DSL test classes using JUnit-5 is something like
The approach to achieve that is described in this Stack-Overflow question and basically suggest to extend the generated
MyDSLInjectorProvider
by overriding itscreateRuntimeModule()
method and return a customized Module that adds the desired bindings. The extended provider can then be used in the test classes.This all works well (AFAICT) but it is a bit inconvenient because by default the
InjectorProvider
is generated as follows:Because the generated
createRuntimeModule()
returns the specific module type generated for the DSL a sub-classes that want to override that method has to return the specific type as well and consequently has to repeat the ceremony to make the injector work in Maven/Tycho and OSGI or one has to overrideinternalCreateInjector()
as it is done inhttps://github.com/eclipse/xtext/blob/73bd1580bcea48d7ed27f990cadbc5ae4d308572/org.eclipse.xtext.testlanguages.ide/src/org/eclipse/xtext/testlanguages/xtextgrammar/ide/XtextGrammarTestLanguageIdeInjectorProvider.java#L24-L35
But this also includes repeating ceremony.
If
createRuntimeModule()
would declare its return type to be justcom.google.inject.Module
then a customized provider could be justIt would be even simpler if the generated
InjectorProvider
would have for example aprotected Module createTestModule()
that by default just returns null and can be overriden in subclasses:A customized
InjectorProvider
would then just beGiven there are no other classes in the package calling
createRuntimeModule()
both approaches should be source compatible with respect to existing sub-classes (a subclass can narrow the return type), but the latter should be 'safer' if custom sub-classes callcreateRuntimeModule()
and expect the specific Module type.Do you know a better solution or do you think that would be a suitable enhancement of Xtext's template for the test
InjectorProvider
?The text was updated successfully, but these errors were encountered: