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

Fix the glue class autowiring, transaction and cucumber-glue scope issues of the spring module #711

Merged

Conversation

brasmusson
Copy link
Contributor

Autowiring one glue class to a field of another glue class does not work. It has been noted, in a comment on another spring issue and on the mailing list, but no issue has been created for it.

This PR fixes the issue with autowiring glue classes . It make the cucumber-glue scope available in the configuration xml files (fixes #600). It also make the transaction support work again (fixes #637). It also loosens the requirement that each glue class needs an @ContextConfiguration/@ContextHierarchy annotation.

The final state is that:

  • One glue class needs to have a @ContextConfiguration/@ContextHierarchy annotation defining the location of the context configuration file ("cucumber.xml").
  • If more than one glue class have a @ContextConfiguration/@ContextHierarchy annotation, all annotations on those classes must be equal.
  • It is only on a class with a @ContextConfiguration/@ContextHierarchy, other annotations are read (like @DirtiesContext or @WebAppConfiguration), they will be applied to the glue as a whole.
  • If not glue class with @ContextConfiguration/@ContextHierarchy annotation is found, it will fallback to use the "cucumber.xml" file on the classpath. If "cucumber.xml" does not exists on the classpath, the final fallback is to create a generic context with only the glue classes in.

@brasmusson brasmusson removed the Blocked label May 6, 2014
@aslakhellesoy
Copy link
Contributor

If you can, please join here Mon, May 12, 3:00 PM - 4:00 PM to discuss: https://plus.google.com/u/0/events/cstdgkbvp61g7gld8lejsap4j3o

@aslakhellesoy
Copy link
Contributor

I'm getting an error:

aslaks-air:spring aslakhellesoy$ mvn clean install
[INFO] Scanning for projects...
[INFO] 
[INFO] Using the builder org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder with a thread count of 1
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building Cucumber-JVM: Spring 1.1.7-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ cucumber-spring ---
[INFO] Deleting /Users/aslakhellesoy/github/cucumber-ltd/cucumber-jvm/spring/target
[INFO] Deleting /Users/aslakhellesoy/github/cucumber-ltd/cucumber-jvm/spring (includes = [**/*.ser], excludes = [])
[INFO] 
[INFO] --- maven-enforcer-plugin:1.0:enforce (enforce-maven) @ cucumber-spring ---
[INFO] 
[INFO] --- maven-enforcer-plugin:1.0:enforce (enforce) @ cucumber-spring ---
[INFO] The requirePluginVersions rule is currently not compatible with Maven3.
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ cucumber-spring ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ cucumber-spring ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 4 source files to /Users/aslakhellesoy/github/cucumber-ltd/cucumber-jvm/spring/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ cucumber-spring ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 8 resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ cucumber-spring ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 21 source files to /Users/aslakhellesoy/github/cucumber-ltd/cucumber-jvm/spring/target/test-classes
[INFO] 
[INFO] --- maven-surefire-plugin:2.17:test (default-test) @ cucumber-spring ---
[INFO] Surefire report directory: /Users/aslakhellesoy/github/cucumber-ltd/cucumber-jvm/spring/target/surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running cucumber.runtime.java.spring.hooks.SpringTransactionHooksTest
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.396 sec - in cucumber.runtime.java.spring.hooks.SpringTransactionHooksTest
Running cucumber.runtime.java.spring.SpringFactoryTest
Tests run: 10, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.203 sec - in cucumber.runtime.java.spring.SpringFactoryTest
Running cucumber.runtime.java.spring_contextconfig.RunCukesTest
ERROR  prepareTestInstance, Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@54329d2f] to prepare test instance [cucumber.runtime.java.spring.AnotherStepDef@46eda3d8]
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cucumber.runtime.java.spring.AnotherStepDef': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: cucumber.runtime.java.spring.OneStepDef cucumber.runtime.java.spring.AnotherStepDef.oneStepDef; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [cucumber.runtime.java.spring.OneStepDef] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:292)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1185)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:384)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:110)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75)
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:319)
    at cucumber.runtime.java.spring.SpringFactory.getTestInstance(SpringFactory.java:148)
    at cucumber.runtime.java.spring.SpringFactory.getInstance(SpringFactory.java:132)
    at cucumber.runtime.java.JavaStepDefinition.execute(JavaStepDefinition.java:35)
    at cucumber.runtime.StepDefinitionMatch.runStep(StepDefinitionMatch.java:37)
    at cucumber.runtime.Runtime.runStep(Runtime.java:297)
    at cucumber.runtime.model.StepContainer.runStep(StepContainer.java:44)
    at cucumber.runtime.model.StepContainer.runSteps(StepContainer.java:39)
    at cucumber.runtime.model.CucumberScenario.run(CucumberScenario.java:48)
    at cucumber.runtime.junit.ExecutionUnitRunner.run(ExecutionUnitRunner.java:83)
    at cucumber.runtime.junit.FeatureRunner.runChild(FeatureRunner.java:63)
    at cucumber.runtime.junit.FeatureRunner.runChild(FeatureRunner.java:18)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at cucumber.runtime.junit.FeatureRunner.run(FeatureRunner.java:70)
    at cucumber.api.junit.Cucumber.runChild(Cucumber.java:89)
    at cucumber.api.junit.Cucumber.runChild(Cucumber.java:40)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at cucumber.api.junit.Cucumber.run(Cucumber.java:94)
    at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:264)
    at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:153)
    at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:124)
    at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:200)
    at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:153)
    at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:103)
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: cucumber.runtime.java.spring.OneStepDef cucumber.runtime.java.spring.AnotherStepDef.oneStepDef; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [cucumber.runtime.java.spring.OneStepDef] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:508)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:289)
    ... 38 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [cucumber.runtime.java.spring.OneStepDef] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1100)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:960)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:855)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:480)
    ... 40 more

4 Scenarios (1 failed, 3 passed)
9 Steps (1 failed, 8 passed)
0m0.309s

@brasmusson
Copy link
Contributor Author

Basically this is what happened:
I disclosed an error in 6f6aba0
I fixed the error in 54d04f7
Then I more or less reverted the fix in 5216717
Somehow it still works on Linux (but I cannot see how), but not on MacOS or Windows.
I'll update the PR

@ffbit
Copy link
Contributor

ffbit commented May 13, 2014

It compiles successfully on my mac

ffbit@ffbit:spring (spring-stepdef-injection-error)$ mvn clean install
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 7.901s
[INFO] Finished at: Tue May 13 10:32:04 EEST 2014
[INFO] Final Memory: 19M/229M
[INFO] ------------------------------------------------------------------------

Java version

java -version
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
java version "1.7.0_55"
Java(TM) SE Runtime Environment (build 1.7.0_55-b13)
Java HotSpot(TM) 64-Bit Server VM (build 24.55-b03, mixed mode)

@brasmusson
Copy link
Contributor Author

Then the updated version have been build successfully on Linux, Windows and MacOS (head at c5a7e38 - GitHub does not list the commits in parent-child order, it must be confused by some date stamps on the commits when I fixed older commits by amending to them)

@ffbit
Copy link
Contributor

ffbit commented May 13, 2014

It seems to me that the underlying persistence context does not flush, when we put the @TxN tag in the see_messages.feature file. So @manytoone relationship does not work between on user messages.

brasmusson and others added 7 commits May 13, 2014 20:30
Use the first class found with spring annotation, to create the
TestContextManagers for all glue classes.
Also remove the @TxN annotation from see_message.feature, since it
fails if transactions are used.
Test that if a step def class autowires another step def class that has
not been used yet. Then it is the instance that was autowired that are
returned to the backend, when asked for an instance.
@brasmusson
Copy link
Contributor Author

Just when I thought everything was working fine, I came up with a new test case for step def injection - and it fails.
So, in this PR I have made the autowiring of a glue class that the backend already requested and used, into the next glue class that the backend requests work (stepdefInjection.feature in 6f6aba0).
But, the other way around, a when a glue class that the backend has not yet used, is instantiated (by the spring TestContextManager itself) to be autowired into another glue class, then it is not that instance that is handed to the backend, when it requests in instance of that glue class, but a second instance is created (updated stepdefInjection.feature in 8ac8f43).

@aslakhellesoy
Copy link
Contributor

@brasmusson could that explain why the scenarios are failing on some machines and not on others? Do you think there is state leaking between scenarios and the execution order matters?

@brasmusson
Copy link
Contributor Author

If changing:

    TestContextManager contextManager = new TestContextManager(type);
    contextManager.prepareTestInstance(instance);

to:

    CucumberTestContextManager contextManager = new CucumberTestContextManager(type);
    contextManager.setParentOnApplicationContext(applicationContext);
    contextManager.prepareTestInstance(instance);

was the solution to make the first step def injection test case to pass, then it is weird that accidentally almost reverting it to:

    TestContextManager contextManager = new TestContextManager(stepClassWithSpringContext);
    contextManager.prepareTestInstance(instance);

instead of:

    CucumberTestContextManager contextManager = new CucumberTestContextManager(stepClassWithSpringContext);
    contextManager.setParentOnApplicationContext(applicationContext);
    contextManager.prepareTestInstance(instance);

could work on any platform (and not only on my machine - on Travis to, but both are Linux). But now that it did work on Linux, it gave a clue that the order things happened was important. So it led me to see that there was a scenario that was not tested.

Now that I fixed the code above to the intended one, I think the first scenario is working consistently on all platforms, but that is of no use, if the the latest scenario does not work.

Let the SpringFactory register the bean definitions of the glue classes
in the bean factory of TestContextManager's context. Use this bean
factoryto create the instances of the glue classes. Alse register the
GlueCodeScope in bean factory of the TestContextManager's context.
Make sure that glue classes with different @ContextConfiguration/
@ContextHierarchy annotations are defined in different packages
(since the SpringFactory will throw an exception if glue classes
are added with @ContextConfiguration/@ContextHierarchy annotations
that are not equal).
if (stepClassWithSpringContext == null) {
stepClassWithSpringContext = stepClass;
} else {
checkAnnotationsEqual(stepClassWithSpringContext, stepClass);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we want to fail if glue classes with different Spring annotations are found?

In real life we often see Spring-y tests that define slightly different contexts and are more or less independent from each other. AFAIU when similar independent step definitions will be loaded by the same runtime (e.g. one JUnit runner class), we'll throw an error in this PR. I'm afraid real users might find that counter-intuitive and inconvenient.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only one Spring test is loaded during each run, but more than one Cucumber step definition is loaded for each scenario. I agree with @brasmusson we should allow only one Spring context. @mgurov while I see advantages in injecting a specific Spring context in JUnit tests, I'd like to see an example of where this is needed in Cucumber. Supporting multiple context is controversial enough to deserve a separate entry in the issue tracker and a deeper discussion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also in Cucumber-JvM v1.1.3 only one spring context was allowed, then it was hardcoded that the file cucumber.xml on the classpath defined the spring context.
From an implementation point of view, probable the biggest problem with allowing multiple context is to find out in which the SpringTransactionHooks need to be in to find the bean implementing the PlatformTransactionManager it needs to do its job.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@paoloambrosio in general I agree that less contexts is better but IMHO it is not the mission of cucumber-jvm to force one or another spring practice.

I'm a bit concerned about the proliferation of the RunCukesTest.javas that we see even in this change, but if you guys do not see an issue with that then fine with me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mgurov I just do not see how it should work in detail. In which of the several context should the SpringTransactionHooks class be put (so it finds the PlatformTransactionManager it needs)? Or should it be forbidden to have more than one context if you use transactions?
And what about glue classes without context annotations, in which context should they be put? Or should we require context annotations on all glue classes (that use spring)? I have got the impression that the requirement that every glue class (that use spring) must have an context annotation is one of the problems the v1.1.6.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brasmusson yes, that can be tricky with the TestContextManager. One of the options could be to (partially) reimplement it for our needs and build the context (or hierarchy of contexts) ourself. But maybe the current solution would be good enough as long as heavy spring module users do not complain.

@brasmusson brasmusson changed the title Issue: the spring step definition injection does not work Fix the glue class autowiring, transaction and cucumber-glue scope issues of the spring module May 19, 2014
In the case that @ContextConfiguration annotation is not used, first
try and use the configuartion file classpath:cucumber.xml, if it does
not exist create en plain spring context with only the glue classes in
it.
@aslakhellesoy
Copy link
Contributor

I'd like to merge this PR to master and release 1.1.8 unless someone has any big issues. You have done some fantastic work here @brasmusson.

Are there any other open Spring-related tickets that we need to deal with or does this cover them all?

@brasmusson
Copy link
Contributor Author

Well, the "missing docs" part of #569 is still a bit weak. On the plus side, with the latest changes on this PR, it is possible to upgrade from v1.1.3 without changing anything with respect to spring. Apart from that I'm not aware of any.

As for a release 1.1.8, I would suggest to also include #713 (Allow empty doc string and data table entries after token replacement from scenario outlines) och #673 (Expose Scenario id to step definitions). Small changes, but still valuable to those that raised underlying issues.

@sebrose
Copy link
Member

sebrose commented May 21, 2014

On Wed, 21 May 2014, at 01:37 AM, Björn Rasmusson wrote:

Well, the "missing docs" part of [1]#569 is still a bit weak. On the
plus side, with the latest changes on this PR, it is possible to
upgrade from v1.1.3 without changing anything with respect to
spring. Apart from that I'm not aware of any.

Missing docs do need work, but a stable release that is backwards
compatible with 1.1.3 is a major step forward.

As for a release 1.1.8, I would suggest to also include [2]#713
(Allow empty doc string and data table entries after token
replacement from scenario outlines) och [3]#673 (Expose Scenario id
to step definitions). Small changes, but still valuable to those
that raised underlying issues.

Reply to this email directly or [4]view it on GitHub.
[2204142__eyJzY29wZSI6Ik5ld3NpZXM6QmVhY29uIiwiZXhwaXJlcyI6MTcxNjI4MDY0N
SwiZGF0YSI6eyJpZCI6MzEzMzY0NzN9fQ==--a059906a956c54fed2c4c577ed98b90dbc
2b46b2.gif]

References

  1. Spring context broken when migrating on 1.1.4 / Missing docs #569
  2. Allow empty doc string and data table entries after token replacement from scenario outlines #713
  3. Expose Scenario id to step definitions #673
  4. Fix the glue class autowiring, transaction and cucumber-glue scope issues of the spring module #711 (comment)

@pberlowski
Copy link

Hi,
We've just started integrating cucumber with spring in one of our projects and the split context came up as a first blocker. I've pulled cucumber-jvm, rebased the master onto the branch and built a version for local use. We'll try this and I'll leave feedback regarding our success/failure. It would be very nice to see this merged and released officially soon!

Cheers!

@pberlowski
Copy link

Well, I've just found out that this pull request makes cucumber-spring dependent on Spring-4.0.2 and is not backwards compatible. We use Spring 3.1 so This is unfortunately of no use to us.

@brasmusson
Copy link
Contributor Author

@pberlowski It is not this pull request in itself that makes cucumber-spring dependent on Spring-4.0.2. Its that fact that it is created after Cucumber-JVM v1.1.6 was released, in which cucumber-spring is dependent on Spring-4.0.2. This pull request can be applied on Cucumber-JVM v1.1.5 also (for instance), in which cucumber-spring is dependent on Spring-3.2.4. But you are right in the sense that the version in which this pull request is merged and released in, that version will have cucumber-spring dependent on Spring-4.0.2

(if applying this pull request to Cucumber-JVM v1.1.5, then SpringFactory.dependsOnSpringContext need to be changed to the version in Cucumber-JVM v1.1.6, because some unit tests in this pull request uses the @ContextHierarchy annotation:

        return type.isAnnotationPresent(ContextConfiguration.class)
            || type.isAnnotationPresent(ContextHierarchy.class);

)

@pberlowski
Copy link

@brasmusson Yeah, I did some digging and came to the same conclusion. Right now we forced our integration test project to use spring 4.0.2. No major vents yet, so we're happy to fly. As I said, I've rebased the current master underneath this pull request and popped that into our nexus. It seems to work fine and does what we initially expected cucumber to do. Many thanks for the solution, I was really surprised when I initially debugged into the SpringFactory...

@DavidGangel
Copy link

Can someone please tell me that the current Cucumber 1.1.7 release which version of Spring is compatible with?

@aslakhellesoy
Copy link
Contributor

Please see CONTRIBUTING.md and the topmost pom.xml for the spring version. Use GitHub to see the pom.xml for the v1.7.7 tag.

@aslakhellesoy
Copy link
Contributor

Sorry, didn't mean to close this.

@aslakhellesoy aslakhellesoy reopened this Jun 11, 2014
paoloambrosio added a commit that referenced this pull request Jun 30, 2014
Fix the glue class autowiring, transaction and cucumber-glue scope issues of the spring module
@paoloambrosio paoloambrosio merged commit 09eac66 into cucumber:master Jun 30, 2014
@paoloambrosio
Copy link
Member

At the Cucumber OSS hack night... we thing it works. Crossing our fingers...

@lock
Copy link

lock bot commented Oct 25, 2018

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@lock lock bot locked as resolved and limited conversation to collaborators Oct 25, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
9 participants