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

application-test.yaml seems to be ignored when test makes second call to DatabaseConfig.loadFromProperties #2997

Open
simontankersley opened this issue Mar 14, 2023 · 2 comments

Comments

@simontankersley
Copy link

Background

Often when testing in Spring, multiple application contexts can be created and destroyed. When using programatic configuration of ebeans this could create a situation where multiple calls are made to DatabaseConfig.loadFromProperties method.

Expected behavior

It doesn't matter how many times the tests call DatabaseConfig.loadFromProperties, the test configuration is found and used each time.

Actual behavior

Only the first call to DatabaseConfig.loadFromProperties succeeds. When trying to use a Database created from the DatabaseConfig that was created by a second call to DatabaseConfig.loadFromProperties we get exception:

    io.ebean.datasource.DataSourceConfigurationException: DataSource user is not set? url is [null]
        at app//io.ebean.datasource.pool.ConnectionPool.<init>(ConnectionPool.java:121)
        at app//io.ebean.datasource.pool.ConnectionPoolFactory.createPool(ConnectionPoolFactory.java:14)
        at app//io.ebean.datasource.DataSourceFactory.create(DataSourceFactory.java:25)
        at app//io.ebeaninternal.server.core.InitDataSource.create(InitDataSource.java:113)
        at app//io.ebeaninternal.server.core.InitDataSource.createFromConfig(InitDataSource.java:104)
        at app//io.ebeaninternal.server.core.InitDataSource.initDataSource(InitDataSource.java:44)
        at app//io.ebeaninternal.server.core.InitDataSource.initialise(InitDataSource.java:33)
        at app//io.ebeaninternal.server.core.InitDataSource.init(InitDataSource.java:24)
        at app//io.ebeaninternal.server.core.DefaultContainer.setDataSource(DefaultContainer.java:220)
        at app//io.ebeaninternal.server.core.DefaultContainer.createServer(DefaultContainer.java:86)
        at app//io.ebeaninternal.server.core.DefaultContainer.createServer(DefaultContainer.java:29)
        at app//io.ebean.DatabaseFactory.createInternal(DatabaseFactory.java:136)
        at app//io.ebean.DatabaseFactory.create(DatabaseFactory.java:85)
        at app//org.example.PetTest.testDouble(PetTest.java:44)

It appears as though the test configuration isn't found.

Steps to reproduce

I've created a sample project with code at https://github.com/simontankersley/programatic-ebean-test

This code fails

        // simulate using a custom object mapper
        ObjectMapper mapper = new ObjectMapper();

        // simulate creating a database configuration twice
        // this often happens when using spring tests where several
        // contexts can be created and destroyed - causing multiple
        // calls to create the database configuration from properties
        DatabaseConfig dbConfig = new DatabaseConfig();
        dbConfig.loadFromProperties();
        dbConfig.setObjectMapper(mapper);
        Database db = DatabaseFactory.create(dbConfig);

        dbConfig = new DatabaseConfig();
        dbConfig.loadFromProperties();
        dbConfig.setObjectMapper(mapper);
        db = DatabaseFactory.create(dbConfig);
        db.save(new Pet());

If you want to see a base line vs a failing test see below

Success when running a test with a single call to DatabaseConfig.loadFromProperties

git clone git@github.com:simontankersley/programatic-ebean-test.git
cd programatic-ebean-test
./gradlew test --tests '*testSingle'

gives

> Task :test

PetTest > testSingle() PASSED

Failure when running a test with a multiple calls to DatabaseConfig.loadFromProperties

./gradlew test --tests '*testDouble'

gives

> Task :test

PetTest > testDouble() FAILED
    io.ebean.datasource.DataSourceConfigurationException: DataSource user is not set? url is [null]
        at app//io.ebean.datasource.pool.ConnectionPool.<init>(ConnectionPool.java:121)
        at app//io.ebean.datasource.pool.ConnectionPoolFactory.createPool(ConnectionPoolFactory.java:14)
        at app//io.ebean.datasource.DataSourceFactory.create(DataSourceFactory.java:25)
        at app//io.ebeaninternal.server.core.InitDataSource.create(InitDataSource.java:113)
        at app//io.ebeaninternal.server.core.InitDataSource.createFromConfig(InitDataSource.java:104)
        at app//io.ebeaninternal.server.core.InitDataSource.initDataSource(InitDataSource.java:44)
        at app//io.ebeaninternal.server.core.InitDataSource.initialise(InitDataSource.java:33)
        at app//io.ebeaninternal.server.core.InitDataSource.init(InitDataSource.java:24)
        at app//io.ebeaninternal.server.core.DefaultContainer.setDataSource(DefaultContainer.java:220)
        at app//io.ebeaninternal.server.core.DefaultContainer.createServer(DefaultContainer.java:86)
        at app//io.ebeaninternal.server.core.DefaultContainer.createServer(DefaultContainer.java:29)
        at app//io.ebean.DatabaseFactory.createInternal(DatabaseFactory.java:136)
        at app//io.ebean.DatabaseFactory.create(DatabaseFactory.java:85)
        at app//org.example.PetTest.testDouble(PetTest.java:44)
@simontankersley simontankersley changed the title application-test.yaml seems to be ignored when test make second call to DatabaseConfig.loadFromProperties application-test.yaml seems to be ignored when test makes second call to DatabaseConfig.loadFromProperties Mar 14, 2023
@rbygrave
Copy link
Member

Thanks for the example project with failing test. I have cloned and reproduced the issue.

Some background:
There are some considerations going on that we need to navigate.

  • We are looking to start the appropriate docker containers once and set them up once (create the database, create the user/role etc)
  • We are similarly looking to run the DDL once to drop and recreate the database schema

The current issue as I see it is that the "Run Once Marker" is covering those things that we do really only want to run once but it is also covering the function that does the "set up the DataSourceConfig given the test docker container" we want to run our tests against.

So I expect the fix for this issue to be along the lines of:

  • moving that "Run Once Marker" to cover only the "setup the test docker containers"
  • always executing the function that does the "set up the DataSourceConfig given the test docker container"
  • adjust the DDL configuration such that we use DDL mode none for subsequent executions (only run the DDL once)

Q: Is there a workaround in the meantime?

Probably look to use explicit url, username, password in application-test.yaml and programmatically set the appropriate ddl mode (which assumes we can determine when the first Ebean DatabaseFactory.create() is happening and setDdlRun(true) only for that creation).

@rbygrave
Copy link
Member

Maybe workaround

A possible workaround is to explicitly specify the dataSource options in application-test.yaml like:

ebean:
  test:
    platform: postgres
    dbName: test
    ddlMode: dropCreate


datasource:
  db:
    username: test
    password: test
    url: jdbc:postgresql://localhost:6432/test

Noting that the tests now pass but we might not be getting what we want in terms of DDL being run. If we add logging of the test output (and adjust a little so we can see when the tests create the Database instances in the logs) we see:

$ ./gradlew clean test

> Task :compileJava
Note: Ebean APT generated 1 query beans, loaded 0 others - META-INF/ebean-generated-info.mf entity-packages: [org.example.model]

> Task :test

PetTest > atestSingle() STANDARD_OUT
    23:23:50.577 [Test worker] TRACE io.avaje.config - load from [resource:application-test.yaml]
    23:23:50.578 [Test worker] INFO  io.avaje.config - Loaded properties from [resource:application-test.yaml] 
    23:23:50.579 [Test worker] DEBUG io.ebean.test - automatic testing config - with ebean.test.platform:postgres name:db environmentDb:null
    23:23:50.583 [ForkJoinPool.commonPool-worker-2] INFO  io.ebean.test - Using jdbc settings - username:test url:jdbc:postgresql://localhost:6432/test driver:org.postgresql.Driver
    23:23:50.586 [ForkJoinPool.commonPool-worker-2] DEBUG io.ebean.test - Docker properties: {postgres.password=test, postgres.driver=org.postgresql.Driver, postgres.extensions=hstore,pgcrypto, postgres.username=test, postgres.version=14, postgres.url=jdbc:postgresql://localhost:6432/test, postgres.port=6432, postgres.dbName=test}
    23:23:50.718 [ForkJoinPool.commonPool-worker-2] TRACE io.ebean.test.containers - sqlRun: select 1 from pg_database where datname = 'test'
    23:23:50.726 [ForkJoinPool.commonPool-worker-2] DEBUG io.ebean.test.containers - Container ut_postgres ready with host:localhost port:6432
    23:23:50.729 [Test worker] DEBUG io.ebean.test - for testing - using FixedEncryptKeyManager() keyVal:simple0123456789
    23:23:50.729 [Test worker] INFO  io.ebean.test - For testing purposes a current user provider has been configured. Use io.ebean.test.UserContext to set current user in tests.
    23:23:50.729 [Test worker] INFO  org.example.PetTest - testSingle ------------------------------
    23:23:50.729 [Test worker] INFO  io.ebean - ebean version: 13.15.0
    23:23:50.777 [Test worker] INFO  io.ebean.datasource - DataSource [db] autoCommit[false] transIsolation[READ_COMMITTED] min[2] max[200] in[40ms]
    23:23:50.908 [Test worker] INFO  io.ebean.DDL - Executing db-drop-all.sql - 1 statements, autoCommit:true
    23:23:50.909 [Test worker] DEBUG io.ebean.DDL - executing 1 of 1 drop table if exists pet cascade
    23:23:50.916 [Test worker] INFO  io.ebean.DDL - Executing db-create-all.sql - 1 statements, autoCommit:false
    23:23:50.917 [Test worker] DEBUG io.ebean.DDL - executing 1 of 1 create table pet (   id                            uuid not null,   constraint p...
    23:23:50.924 [Test worker] INFO  io.ebean.core - Started database[db] platform[POSTGRES] in 192ms
    23:23:50.927 [Test worker] DEBUG io.ebean.SQL - txn[1001] insert into pet (id) values (?); -- bind(ca527473-577c-418c-9028-8e667b58e4c3)
    23:23:50.931 [Test worker] DEBUG io.ebean.TXN - txn[1001] Commit

PetTest > testTDouble() STANDARD_OUT
    23:23:50.934 [Test worker] DEBUG io.ebean.test - automatic testing config - with ebean.test.platform:postgres name:db environmentDb:null
    23:23:50.934 [Test worker] DEBUG io.ebean.test - for testing - using FixedEncryptKeyManager() keyVal:simple0123456789
    23:23:50.934 [Test worker] INFO  io.ebean.test - For testing purposes a current user provider has been configured. Use io.ebean.test.UserContext to set current user in tests.
    23:23:50.934 [Test worker] INFO  org.example.PetTest - testDouble 1 ------------------------------
    23:23:50.967 [Test worker] INFO  io.ebean.datasource - DataSource [db] autoCommit[false] transIsolation[READ_COMMITTED] min[2] max[200] in[32ms]
    23:23:50.972 [Test worker] INFO  io.ebean.core - Started database[db] platform[POSTGRES] in 37ms
    23:23:50.972 [Test worker] DEBUG io.ebean.test - automatic testing config - with ebean.test.platform:postgres name:db environmentDb:null
    23:23:50.972 [Test worker] DEBUG io.ebean.test - for testing - using FixedEncryptKeyManager() keyVal:simple0123456789
    23:23:50.972 [Test worker] INFO  io.ebean.test - For testing purposes a current user provider has been configured. Use io.ebean.test.UserContext to set current user in tests.
    23:23:50.972 [Test worker] INFO  org.example.PetTest - testDouble 2 ------------------------------
    23:23:50.998 [Test worker] INFO  io.ebean.datasource - DataSource [db] autoCommit[false] transIsolation[READ_COMMITTED] min[2] max[200] in[26ms]
    23:23:51.002 [Test worker] INFO  io.ebean.DDL - Executing db-drop-all.sql - 1 statements, autoCommit:true
    23:23:51.002 [Test worker] DEBUG io.ebean.DDL - executing 1 of 1 drop table if exists pet cascade
    23:23:51.005 [Test worker] INFO  io.ebean.DDL - Executing db-create-all.sql - 1 statements, autoCommit:false
    23:23:51.005 [Test worker] DEBUG io.ebean.DDL - executing 1 of 1 create table pet (   id                            uuid not null,   constraint p...
    23:23:51.008 [Test worker] INFO  io.ebean.core - Started database[db] platform[POSTGRES] in 36ms
    23:23:51.009 [Test worker] DEBUG io.ebean.SQL - txn[1001] insert into pet (id) values (?); -- bind(02036faf-fc6c-4f5c-96f2-fac25f06e1d3)
    23:23:51.010 [Test worker] DEBUG io.ebean.TXN - txn[1001] Commit
23:23:51.016 [EbeanHook] INFO  io.ebean.datasource - DataSource [db] shutdown min[2] max[200] free[2] busy[0] waiting[0] highWaterMark[1] waitCount[0] hitCount[4]  psc[hit:0 miss:4 put:4 rem:0]
23:23:51.016 [EbeanHook] INFO  io.ebean.datasource - DataSource [db] shutdown min[2] max[200] free[2] busy[0] waiting[0] highWaterMark[1] waitCount[0] hitCount[2]  psc[hit:0 miss:1 put:1 rem:0]
23:23:51.017 [EbeanHook] INFO  io.ebean.datasource - DataSource [db] shutdown min[2] max[200] free[2] busy[0] waiting[0] highWaterMark[1] waitCount[0] hitCount[4]  psc[hit:0 miss:4 put:4 rem:0]

BUILD SUCCESSFUL in 1s
7 actionable tasks: 7 executed

... and we can see the ddl being executed multiple times. This might be ok but generally it is not good when we have a bigger schema (ddl takes time) or when we desire to run tests in parallel. Generally the desire is to drop and create the db schema once for all tests.

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

2 participants