Error running tests in Bitucket pipeline: ApplicationContext is not initialized

I’m working on setting up a CI pipeline on bitbucket for quite a while now and I’m not succeeding.
On the local machine I’m runnng the tests successfully with ./gradlew test
When the tests are running in the pipeline I’m getting the following errors:

java.lang.IllegalStateException: ApplicationContext is not initialized at com.haulmont.cuba.core.global.AppBeans.getBeanLocator(AppBeans.java:112) at com.haulmont.cuba.core.global.AppBeans.get(AppBeans.java:41) at dental.gc.userlifecyclemgmt.core.LoadExtUserIntegrationTest.beforeAll(LoadExtUserIntegrationTest.java:21) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:675) at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60) at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:125) at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:132) at org.junit.jupiter.engine.extension.TimeoutExtension.interceptLifecycleMethod(TimeoutExtension.java:111) at org.junit.jupiter.engine.extension.TimeoutExtension.interceptBeforeAllMethod(TimeoutExtension.java:60) at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115) at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105) at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:104) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:62) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:43) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:3

Here is my build.gradle (I believe that could be cleaned up)

buildscript {
    ext.cubaVersion = '7.2.13'
    repositories {
        mavenLocal()
        maven {
            url 'https://repo.cuba-platform.com/content/groups/work'
            credentials {
                username(rootProject.hasProperty('repoUser') ? rootProject['repoUser'] : 'cuba')
                password(rootProject.hasProperty('repoPass') ? rootProject['repoPass'] : 'cuba123')
            }
        }
        maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
        maven {
            url 'https://repo.repsy.io/mvn/edwink/gcdental'
            credentials {
                username('xxx')
                password('xxx')
            }
        }
        jcenter()
    }
    dependencies {
        classpath "com.haulmont.gradle:cuba-plugin:$cubaVersion"
    }
}

def modulePrefix = 'ulms'

def globalModule = project(":${modulePrefix}-global")
def coreModule = project(":${modulePrefix}-core")
def webModule = project(":${modulePrefix}-web")

def servletApi = 'javax.servlet:javax.servlet-api:3.1.0'

apply(plugin: 'cuba')
	
cuba {
    artifact {
        group = 'dental.gc.userlifecyclemgmt'
        version = '0.1'
        isSnapshot = true
    }
}

dependencies {
    appComponent("com.haulmont.cuba:cuba-global:$cubaVersion")
    appComponent('dental.gc.base:gc-base-global:0.2')
}

def postgres = 'org.postgresql:postgresql:42.2.9'

configure([globalModule, coreModule, webModule]) {
    apply(plugin: 'java')
    apply(plugin: 'maven')
    apply(plugin: 'cuba')

    dependencies {
        testCompile('org.junit.jupiter:junit-jupiter-api:5.5.2')
        testCompile('org.junit.jupiter:junit-jupiter-engine:5.5.2')
        testCompile('org.junit.vintage:junit-vintage-engine:5.5.2')
        testCompile('dental.gc.base:gc-base-global:0.2')
    }

    task sourceJar(type: Jar) {
        from file('src')
        classifier = 'sources'
    }

    artifacts {
        archives sourceJar
    }
    test {
        useJUnitPlatform()
    }
}


configure(globalModule) {
    dependencies {
        if (!JavaVersion.current().isJava8()) {
            runtime('javax.xml.bind:jaxb-api:2.3.1')
            runtime('org.glassfish.jaxb:jaxb-runtime:2.3.1')
        }
    }

    entitiesEnhancing {
        main {
            enabled = true
        }
    }
}

configure(coreModule) {

    configurations {
        jdbc
        dbscripts
    }

    dependencies {
        compile(globalModule)
        compileOnly(servletApi)
        jdbc(postgres)
        testRuntime(postgres)
    }

    task cleanConf(description: 'Cleans up conf directory', type: Delete) {
        delete "$cuba.appHome/${modulePrefix}-core/conf"
    }

    task deploy(dependsOn: [assemble, cleanConf], type: CubaDeployment) {
        appName = "${modulePrefix}-core"
        appJars(modulePrefix + '-global', modulePrefix + '-core')
    }

    task createDb(dependsOn: assembleDbScripts, description: 'Creates local database', type: CubaDbCreation) {
    }

    task updateDb(dependsOn: assembleDbScripts, description: 'Updates local database', type: CubaDbUpdate) {
    }
}

configure(webModule) {
    configurations {
        webcontent
    }

    dependencies {
        compileOnly(servletApi)
        compile(globalModule)
    }

    task webArchive(type: Zip) {
        from file("$buildDir/web")
        from file('web')
        classifier = 'web'
    }

    artifacts {
        archives webArchive
    }

    task deployConf(type: Copy) {
        from file('src')
        include "dental/gc/userlifecyclemgmt/**"
        into "$cuba.appHome/${modulePrefix}/conf"
    }	

    task clearMessagesCache(type: CubaClearMessagesCache) {
        appName = "${modulePrefix}"
    }
    deployConf.dependsOn clearMessagesCache

    task cleanConf(description: 'Cleans up conf directory', type: Delete) {
        delete "$cuba.appHome/${modulePrefix}/conf"
    }

    task deploy(dependsOn: [assemble, cleanConf], type: CubaDeployment) {
        appName = "${modulePrefix}"
        appJars(modulePrefix + '-global', modulePrefix + '-web')
    }

    task buildScssThemes(type: CubaWebScssThemeCreation)

    task deployThemes(type: CubaDeployThemeTask, dependsOn: buildScssThemes)

    assemble.dependsOn buildScssThemes

    task themesJar(type: Jar) {
        from file('themes')
        classifier = 'themes'
    }

    artifacts {
        archives themesJar
    }
}

task undeploy(type: Delete, dependsOn: ":${modulePrefix}-web:cleanConf") {
    delete("$cuba.tomcat.dir/shared")
    delete("$cuba.tomcat.dir/webapps/${modulePrefix}-core")
    delete("$cuba.tomcat.dir/webapps/${modulePrefix}")
}

task restart(dependsOn: ['stop', ":${modulePrefix}-core:deploy", ":${modulePrefix}-web:deploy"], description: 'Redeploys applications and restarts local Tomcat') {
    doLast {
        ant.waitfor(maxwait: 6, maxwaitunit: 'second', checkevery: 2, checkeveryunit: 'second') {
            not {
                socket(server: 'localhost', port: '8787')
            }
        }
    }
}
restart.finalizedBy start

task buildWar(type: CubaWarBuilding) {
    appProperties = ['cuba.automaticDatabaseUpdate': true]
    logbackConfigurationFile = 'etc/war-logback.xml'
    includeJdbcDriver = true
    singleWar = false
    appName = ''
}

task buildUberJar(type: CubaUberJarBuilding) {
    logbackConfigurationFile = 'etc/uber-jar-logback.xml'
    distributionDir = "docker-image/uberJar"
    singleJar = true
    appProperties = ['cuba.automaticDatabaseUpdate': true,
                     'cuba.dataSource.username'    : 'postgres',
                     'cuba.dataSource.password'    : 'postgres',
                     'cuba.dataSource.dbName'      : 'ulms',
                     'cuba.dataSource.host'        : 'postgres',
                     'cuba.dataSource.port'        : '',
                     'cuba.dataSourceProvider'     : 'application']
}	

Anything else that might be helpful?

and my bitbucket-pipeline.yml

image: gradle:5.5.1-jdk11

pipelines:
  default:
    - parallel:
        - step:
            name: Build and Test
            caches:
              - gradle
            script:
              - gradle wrapper --gradle-version 5.5
              - gradle build

Not much going on here. Which would rise my first question… Why are the tests running in the first place?

I am also aware that I need to run a service for the database and use a local.app.properties file specifically for this pipeline to connect to the service container… I do however not find a proper documentation to guide me through the correct steps.

Please help!

I almost got it running. So I did the following.

I added the parameter --debug to my ./gradlew build command to see what’s going on
As expected the problem was with connecting to the database
I added a docker service container for Postgres in my bitbucket-pipeline.yml file, which looks now as follows

pipelines:
  default:
      - step:
          name: Build and Test
          services:
            - postgres
          caches:
            - gradle
         script:
           - cp pipeline-local.app.properties modules/core/src/dental/gc/userlifecyclemgmt/app.properties
           - gradle wrapper --gradle-version 5.6
           - ./gradlew build --debug
definitions:
  services:
    postgres:
      image: postgres:9-alpine3.14
      environment:
        POSTGRES_DB: 'pipelinedb'
        POSTGRES_USER: 'granddad'
        POSTGRES_PASSWORD: 'i_want_in'

I created a pipeline.app.properties file with pipeline specific configurations, which replaces the app.properties file (see above)

My tests are running now as long as they are not requesting any data from the database. If they do I get the following error:

java.lang.RuntimeException: Error loading DB-stored app properties cache at com.haulmont.cuba.core.app.ConfigStorage.loadCache(ConfigStorage.java:135) at com.haulmont.cuba.core.app.ConfigStorage.getDbProperty(ConfigStorage.java:106) at com.haulmont.cuba.core.sys.ConfigPersisterImpl.getProperty(ConfigPersisterImpl.java:52) at com.haulmont.cuba.core.config.ConfigGetter.getProperty(ConfigGetter.java:110) at com.haulmont.cuba.core.config.ConfigGetter.getProperty(ConfigGetter.java:95) at com.haulmont.cuba.core.config.ConfigGetter.invoke(ConfigGetter.java:67) at com.haulmont.cuba.core.config.ConfigHandler.invoke(ConfigHandler.java:79) at com.sun.proxy.$Proxy83.getDataManagerChecksSecurityOnMiddleware(Unknown Source) at com.haulmont.cuba.core.app.RdbmsStore.isAuthorizationRequired(RdbmsStore.java:1014) at com.haulmont.cuba.core.app.RdbmsStore.loadList(RdbmsStore.java:209) at com.haulmont.cuba.core.app.RdbmsStore$FastClassBySpringCGLIB$f20c7b33.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:771) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691) at com.haulmont.cuba.core.app.RdbmsStore$EnhancerBySpringCGLIB$4848f4d5.loadList(<generated>) at com.haulmont.cuba.core.app.DataManagerBean.loadList(DataManagerBean.

I believe the database is not being built properly. My suspicion is that the database scripts are not running. So I added the following line to my pipeline-local.app.properties file

cuba.automaticDatabaseUpdate = true

Unfortunately this didn’t make a difference. I also changed the postgres version of the service container to 13.4

Hi,
Note that you don’t need to create additional local.app.properties files in order to set some application properties for tests.

Application properties can be set passed to the running application as well as the process running tests - via environment variables.
See documentation: Application Properties - CUBA Platform. Developer’s Manual

So cuba.automaticDatabaseUpdate = true can be passed as environment variable with name cuba.automaticDatabaseUpdate or CUBA_AUTOMATICDATABASEUPDATE.

Also you need to find a way how to find all log messages from the CI tool after the tests run. The exception you present isn’t enough to understand what has gone wrong.
Maybe this link will help: testing - Gradle: How to Display Test Results in the Console in Real Time? - Stack Overflow

test {
testLogging.showStandardStreams = true
}

The gradlew build command implies building the project AND running tests.

1 Like

Hi there,

thanks for the reply, I attached the complete

gradlew build --debug output to this message.

I’m sure the clue must be in there to see what’s wrong. I know that the application connects to the database and I’m 99.8% certain that the DB scripts are not executed.The question is why… There is only a very small amount of tests at the moment (which are running fine locally) and of which are 2 accessing the database and they fail.

pipelineLog-34.txt (2.9 MB)

You can run “gradlew updateDb” task to run all database creation scripts, before running tests.
https://doc.cuba-platform.com/manual-7.2/build.gradle_updateDb.html

Database connection parameters should be also passed to this task as environment variables.

14:52:00.110 [DEBUG] [TestEventLogger]         Caused by:
14:52:00.110 [DEBUG] [TestEventLogger]         java.sql.SQLException: ERROR: relation "sys_config" does not exist
14:52:00.110 [DEBUG] [TestEventLogger]           Position: 26 Query: select NAME, VALUE_ from SYS_CONFIG Parameters: []
14:52:00.110 [DEBUG] [TestEventLogger]             at com.haulmont.bali.db.QueryRunner.rethrow(QueryRunner.java:464)
14:52:00.110 [DEBUG] [TestEventLogger]             at com.haulmont.bali.db.QueryRunner.query(QueryRunner.java:322)
14:52:00.110 [DEBUG] [TestEventLogger]             at com.haulmont.bali.db.QueryRunner.query(QueryRunner.java:413)
14:52:00.110 [DEBUG] [TestEventLogger]             at com.haulmont.bali.db.QueryRunner.query(QueryRunner.java:388)
14:52:00.110 [DEBUG] [TestEventLogger]             at com.haulmont.bali.db.QueryRunner.query(QueryRunner.java:432)
14:52:00.110 [DEBUG] [TestEventLogger]             at com.haulmont.cuba.core.app.ConfigStorage.loadCache(ConfigStorage.java:123)
14:52:00.110 [DEBUG] [TestEventLogger]             ... 110 more

That’s right, the table doesn’t exist.

1 Like

That’s the one! Great, now it works… for our fellow Cuba developers: here is my final bitbucket-pipeline.yml for test automation.

image: gradle:5.6.4-jdk11

pipelines:
  default:
      - step:
          name: Build and Test
          services:
            - postgres
          caches:
            - gradle
          script:
            - gradle wrapper --gradle-version 5.6
#          Loading pipeline specific configuration   
            - cp pipeline-local.app.properties modules/core/src/dental/gc/userlifecyclemgmt/app.properties
            - ./gradlew updateDb
            - ./gradlew build --debug
definitions:
  services:
    postgres:
      image: postgres:13.4
      environment:
        POSTGRES_DB: 'pipelinedb'
        POSTGRES_USER: 'granddad'
        POSTGRES_PASSWORD: 'i_want_in'
1 Like