objectbox / objectbox-java

Android Database - first and fast, lightweight on-device vector database
https://objectbox.io
Apache License 2.0
4.33k stars 297 forks source link

Android Studio/Kotlin (Jetpack Compose) Test DB is not created using `File("directory/test-database-file-name")` #1175

Closed alghe-global closed 2 months ago

alghe-global commented 2 months ago

Is there an existing issue?

Build info

Steps to reproduce

  1. define an Entity that has at least 3 parameters besides id
  2. build the project in order to generate MyObjectBox etc.
  3. write tests that run queries on the parameters (using contains or equal)
  4. setup setUp() and tearDown() as per documentation for a custom test db (locally)

Expected behavior

All tests should pass due to database being setup and teared down from scratch for each test.

Actual behavior

Database is persisted across tests thus failing due to clashing entities.

PLEASE NOTE: I DO NOT want to enable assignable = true on id.


Debugging steps

I've ran the following command to see whether the test db is created and noticed only the directory is created (not the file) which means there is a single db used instead of a new created (after the previous is destroyed) for each test:

while true; do echo -e "\n\n----[ $(date) ]----\n\n"; ls -lhiaR * | egrep -i --color "objectbox-example|test-db" ; echo -e "\n\n----------------------------------------\n\n\n\n\n"; sleep 1 ; done
How to use
  1. have Android Studio ready for tests to be ran
  2. start the shell command
  3. run tests in Android Studio
  4. observe command output (only the directory is detected, not the file)
  5. stop (CTRL-C) the command in shell once Android Studio tests finished

Code

This is sample code and may not necessarily represent the code with which the error was triggered.

Code ```kotlin @Entity data class Image( @Id var id: Long = 0, @Index var timestamp: Long? = null, var location: String? = null, var owner: String? = null, ) { @Backlink(to = "image") lateinit var users: ToMany } @Entity data class ImageUser( @Id var id: Long = 0, var user: String? = null ) { lateinit var image: ToOne } ``` ```kotlin object MyEntitiesMockDataSource { val entityList = listOf( Image( timestamp = 0L, location = "image1Location", owner = "email@example.com" ), Image( timestamp = 1L, location = "image2Location", owner = "email@example.com" ), Image( timestamp = 2L, location = "image3Location", owner = "email@example.com" ) } ``` ```kotlin @Test fun testGetLatest10EntitiesByOwner_populatedBox_emitsExpectedEntitiesByOwner() = runTest { val owner = "email@example.com" val entities = MyEntitiesMockDataSource.entityList /** Insert test entities into the database */ boxStore.boxFor().put(entities) repository.getLatest10EntitiesByOwner(owner).test { val emission = awaitItem() awaitComplete() /** reversed() because the query does orderDesc() */ assertEquals(entities.reversed(), emission) } } ```

Logs, stack traces

Logs ```console java.lang.IllegalArgumentException: ID is higher or equal to internal ID sequence: 1 (vs. 1). Use ID 0 (zero) to insert new entities. ```
alghe-global commented 2 months ago

Please note, the tests should be multiple testing different sort of queries on the test database using the same event - normally, the setUp and tearDown should take care of having a clean database for each test, however, this is not what's observed.

alghe-global commented 2 months ago

Nevermind, ran once again and the file is created (appended * to the search terms for egrep) - it seems sleep 1 was too much (the tests ran too quickly). The error still persists, though seems unrelated to this issue. I tried without coroutines and two of the three tests that store events and operate queries still fail.

alghe-global commented 2 months ago

Closing as Won't fix.

alghe-global commented 2 months ago

Just a note, I tried with TEST_DIRECTORY.delete() instead of BoxStore.deleteAllFiles(TEST_DIRECTORY) and the tests that were failing before now succeeded, though the tests that tested for empty data failed (as there was data populated).

alghe-global commented 2 months ago

Close as Won't fix.

alghe-global commented 2 months ago

Running tests one by one is successful so there's something definitely running the tests all together that fails.

alghe-global commented 2 months ago

Ran out of solutions, this is clearly an issue with setUp() and tearDown() - reopening in hopes of an investigation from @greenrobot-team .

Team, I've uploaded run.log - this log demonstrates that the directory (TEST_DIRECTORY) exists and that the database is created, however, the database is not cleaned up in between tests. What I think is going on:

  1. the database is created
  2. the tests that test empty database succeed
  3. the tests that write to database have the following outcome: first test that gets to write to the database succeeds, the following two fail because the database is already populated

You can tell by searching for the keyword "db" in run.log file. You'll see before that no database exists, then for a period of time the database persists, then the database is deleted. What we should have observed is that the database gets created and deleted multiple times (for the setUp() and tearDown() executions) - however, this doesn't seem to be the case.

I couldn't reproduce this with objectbox-examples.

alghe-global commented 2 months ago

Reproduced with https://github.com/alghe-global/Kotlin/commit/0b4576f34894d57fe37592a5a6be74a0319b4395

greenrobot-team commented 2 months ago

java.lang.IllegalArgumentException: ID is higher or equal to internal ID sequence: 1 (vs. 1). Use ID 0 (zero) to insert new entities.

This error means that one of your tests is inserting an entity with an ID set. My best guess is that the entity list that is used in your tests is not re-generated between tests. Note that on put() ObjectBox will update an entity with the ID it was assigned.

Or put differently, try to make this a function that builds the list each time, not a variable that keeps the entities between tests:

val entities = MyEntitiesMockDataSource.entityList

Let me know if that works for you.

alghe-global commented 2 months ago

Indeed, using a function to generate the mock data works and tests pass successful. Thanks for your help!


A question before we close here so I can learn more about ObjectBox internals: it seems that ObjectBox is tracking state irrespective of entering data into the database (by the instantiation of Entities) - is this true? Otherwise, why would the put() method fail if the database was destroyed and recreated for each of the test? Wouldn't the database start from scratch and simply accept the same IDs previously generated for the other tests too?

greenrobot-team commented 2 months ago

Wouldn't the database start from scratch and simply accept the same IDs previously generated for the other tests too?

By default, new entities must be put with ID 0. If an entity with an ID > 0 is put, the database assumes it exists and tries to update it. See https://docs.objectbox.io/getting-started#object-ids for more details.

So closing this then!