Search code examples
hibernategrailslockingcucumbergrails-orm

Optimistic lock failure with cucumber tests data load and grails 2.3.8


I am attempting to delete and create data using GORM, in the Background phase of my cucumber tests. When I attempt to delete an object in the Background that was changed in the previous scenario, I get what looks like an optimistic locking violation. I am using grails 2.3.8, and hibernate 3.6.10.13, and cucumber 0.10.0. I get the same error with either mysql or the H2 database.

This is the error message:

    | Error 2014-05-02 14:24:09,092 [main] ERROR events.PatchedDefaultFlushEventListener  - Could not synchronize database state with session
    Message: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.example.angugrails.auth.User#170]
    Line | Method
    ->>  13 | doCall        in steps.DataSteps$_run_closure1_closure3
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    |    11 | doCall        in steps.DataSteps$_run_closure1
    |   146 | invoke . . .  in cucumber.runtime.groovy.GroovyBackend
    |    67 | call          in cucumber.runtime.groovy.GroovyStepDefinition$1
    |    12 | timeout . . . in cucumber.runtime.Timeout
    |    64 | execute       in cucumber.runtime.groovy.GroovyStepDefinition
    |    38 | runStep . . . in cucumber.runtime.StepDefinitionMatch
    |   289 | runStep       in cucumber.runtime.Runtime
    |    44 | runStep . . . in cucumber.runtime.model.StepContainer
    |    39 | runSteps      in     ''
    |    49 | runBackground in cucumber.runtime.model.CucumberScenario
    |    38 | run           in     ''
    |   116 | run . . . . . in cucumber.runtime.model.CucumberFeature
    |    59 | doCall        in grails.plugin.cucumber.Cucumber$_run_closure2
    |    58 | run . . . . . in grails.plugin.cucumber.Cucumber
    |   121 | runFeatures   in grails.plugin.cucumber.CucumberTestType
    |    57 | doRun . . . . in     ''
    |   102 | doCall        in _GrailsTest_groovy$_run_closure1
    |    32 | doCall . . .  in TestApp$_run_closure1
    ^   120 | main          in com.intellij.rt.execution.application.AppMain

This is the hibernate config from DataSource.groovy, I am attempting to disable the second_level_cache because perhaps cacheing objects retrieved in the cucumber test could cause this problem:

hibernate {
    cache.use_second_level_cache = false
    cache.use_query_cache = false
    cache.region.factory_class = 'net.sf.ehcache.hibernate.EhCacheRegionFactory' // Hibernate 3
//    cache.region.factory_class = 'org.hibernate.cache.ehcache.EhCacheRegionFactory' // Hibernate 4
    singleSession = true // configure OSIV singleSession mode
}

The Background of the feature file looks like this:

Feature: User Profile
  Background:
    Given the db is reset
    Given the db is loaded with new user. username: "user1" email: "testuser1@example.com" 
    password: "testpassword1"

The database cucumber step functions look like this:

Given(~'^the db is reset') { ->
    User.findAll().each {
        it.delete(flush: true)
    }
 }

When(~'^the db is loaded with new user. username: "([^"]*)" email: "([^"]*)" password: "([^"]*)"$') {
    String username, String email, String password ->
        def user = new User(username: username, email: email, password: password, enabled: true, accountLocked: false)
        if (!user.validate()) {
            throw new Exception("unexpected error.")
        } else {
            user.save(flush: true)
            def debug = User.findByUsername("user1")
            debug.username
        }
}

The scenario that I'm running uses webdriver and the web interface to change the user's password. Once that scenario is run, the Background will fail with the above error for all subsequent scenarios in the feature.

All my tests that do not change any user attributes succeed. How do I avoid this optimistic locking error?

Thanks!


Solution

  • You should not have queries, in particular writing queries in steps. Move that code into a service to get proper transaction/commit handling.

    This is true for normal Grails too but, in my experience, it doesn't show up so directly.

    I like to call that cleanup from a @Before hook. That keeps that technical stuff like 'the db is reset' out of the scenarios.

    Note that directly calling grails stuff from steps is deprecated for new code since grails 2.3. Current style will still work in grails non forked mode but it will never work in forked mode. See this article.