Skip to content
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

Accessing Spring from custom extension: Change global extension execution order #817

Open
tburny opened this issue Jan 26, 2018 · 4 comments

Comments

@tburny
Copy link
Contributor

tburny commented Jan 26, 2018

Issue description

Currently global extensions are executed in the order they are discovered in the class path. That poses a problem when writing custom extensions which try to access fields in the Specification which will be initialized by Spring via @Autowired.
When I have MyGlobalExtension in MyProject, which depends on spock-spring and last on spock-core, the Spring extension will always add its IMethodInterceptors after MyGlobalExtension.

As far as I understand, there are a few possible workarounds (solutions) to the problem:

  • Allow defining the order other extensions by listing them META-INF/services/org.spockframework.runtime.extension.IGlobalExtension, so the globalExtensionClasses in GlobalExtensionRegistry would probably become a LinkedHashSet<Class<?>>. This would make the order of extensions configurable explicitly. (BUG: Currently the SpringExtension will be loaded twice if you do add it to your own extension configuration file)
  • Reverse the order of Extensions before execution in ExtensionRunner:
  private void runGlobalExtensions() {
    final List<IGlobalExtension> globalExtensions = new ArrayList<IGlobalExtension>(extensionRegistry.getGlobalExtensions());
    Collections.reverse(globalExtensions);
    for (IGlobalExtension extension : globalExtensions) {
      extension.visitSpec(spec);
    }
  }

This ensures that all extensions in dependencies are loaded first, but would not fix the problem between extensions in different dependencies on the classpath

  • Always load the Spring extension first programatically

I will happily contribute a fix along with tests and a Gist if required :)

Java/JDK

java -version
1.7+

Groovy version

Note that versions older than 2.0 are no longer supported.

groovy -version
2.4

Build tool version

Gradle

gradle -version
Gradle wrapper from spock-example project

Operating System

Windows 10

IDE

IntelliJ

Build-tool dependencies used

Gradle/Grails

compile 'org.spockframework:spock-core:1.1-groovy-2.4'
compile 'org.spockframework:spock-spring:1.1-groovy-2.4'
@leonard84
Copy link
Member

releates #646

@tburny
Copy link
Contributor Author

tburny commented Feb 7, 2018

Thanks a lot for replying so quickly! Your are perfectly right in your comment in the pull request, sorry for wasting your time :-/

So as a result I took a step back, reconsidering what needs to be done.

In my opinion the problem could be split into three more or less independent components:

  • Declaring dependencies either via annotaiton or interface
  • Building and error-checking a dependency graph (keyword: Cycle detection)
  • Resolving and loading dependencies

I would like to suggest solving the first one and then moving on to the others 👍

Declaring dependencies of an extension

As an extension developer I would like to declare dependencies on other extensions so these are loaded first (happens-before relationship).

This has to be done before starting the extension and can be implemented

  • via an annotation, executed after all Extensions were discovered, but not instantiated

    @DependsOn([Foo, Bar, Baz])
    class MyExtension {}
    • Advantages: Static resolution: Dependencies can be resolved before the extension is instanciatiated
    • Disadvantages: Less flexible, cannot declare dependencies conditionally (e.g. depending on System environment)
  • via an interface, executed after the extensions are loaded, but not started yet

    interface IExtensionDependencies { Set<Class<?>> getDependencies() }
    
    class MyExtension extends AbstractGlobalExtension implements IExtensionDependencies {
    	
      @Override
      Set<Class<?>> getDependencies(List<Class<?>> availableExtensions) { return [Foo, Bar].toSet() }
    }

    The availableExtensions parameter contains a list of all known extensions. This allows declaring a dependency if it is present (i.e. "load SpringExtension, if available").

    • Advantages: Most flexible solution, can declare dependencies flexibly within normal groovy code
    • Disadvantages: No static dependency analysis on the extension class (instance only).

As far as I understand both mechanisms could be used on global and annotation driven extensions.
What do you think?

P.S.: I'm also available on Gitter.im (gitter.im/tburny) if you prefer discussing this on the spock channel/via DM :)

@leonard84
Copy link
Member

I'd like to avoid having to do a full dependency tree calculation for each test, for global extensions this is so much of an issue, but annotation based ones can vary for each test. I was thinking of using the simple ordering style (simple int), but that too has limitations.

Only having dependencies might be a bit limiting, you can not express that you'd like to run before a certain extension.

Ordering just the extensions might also be not enough, since extensions usually work by adding interceptors, which do the actual work. Normally you'd want to have a certain order going in (setup...) and the reverse order going out (cleanup...). So you actually want to order interceptors and not the extensions themselves.

@xukaizai
Copy link

xukaizai commented May 6, 2019

how to use this change "Fix #817" by using maven (1.3-groovy-2.x)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants