diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/contacts/ContactsManagerIntegrationTest.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/contacts/ContactsManagerIntegrationTest.kt index ffa34af39653988f58da63d9a0d8849287791240..aa9f5454182b86bec474106d88376dbfcdedf349 100644 --- a/mailbox-core/src/test/java/org/briarproject/mailbox/core/contacts/ContactsManagerIntegrationTest.kt +++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/contacts/ContactsManagerIntegrationTest.kt @@ -27,8 +27,8 @@ import kotlin.test.assertEquals class ContactsManagerIntegrationTest : IntegrationTest() { @BeforeEach - override fun initDb() { - super.initDb() + override fun beforeEach() { + super.beforeEach() addOwnerToken() } diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/contacts/ContactsManagerMalformedInputIntegrationTest.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/contacts/ContactsManagerMalformedInputIntegrationTest.kt index 0f7456792197b2a6bd14f3f0ec549b1f4dc470c5..6acd4fa506e2b53d9a21a3cf0cb8a726e9c48b59 100644 --- a/mailbox-core/src/test/java/org/briarproject/mailbox/core/contacts/ContactsManagerMalformedInputIntegrationTest.kt +++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/contacts/ContactsManagerMalformedInputIntegrationTest.kt @@ -19,8 +19,8 @@ import kotlin.test.assertEquals class ContactsManagerMalformedInputIntegrationTest : IntegrationTest(false) { @BeforeEach - override fun initDb() { - super.initDb() + override fun beforeEach() { + super.beforeEach() addOwnerToken() } diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/files/FileManagerIntegrationTest.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/files/FileManagerIntegrationTest.kt index fd0d67b97374bf1a1ce6a4865ea2cbe3fa801430..cd92398a51cfbb33d007f1e377cbac9b4e55260a 100644 --- a/mailbox-core/src/test/java/org/briarproject/mailbox/core/files/FileManagerIntegrationTest.kt +++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/files/FileManagerIntegrationTest.kt @@ -23,8 +23,8 @@ class FileManagerIntegrationTest : IntegrationTest() { private val bytes = Random.nextBytes(2048) @BeforeEach - override fun initDb() { - super.initDb() + override fun beforeEach() { + super.beforeEach() addOwnerToken() addContact(contact1) addContact(contact2) diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/server/IntegrationTest.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/server/IntegrationTest.kt index bbb9f151b9da66d78110d5b3687f2be17be3807c..a628a963de01f3e3064be432464ce2de401e5898 100644 --- a/mailbox-core/src/test/java/org/briarproject/mailbox/core/server/IntegrationTest.kt +++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/server/IntegrationTest.kt @@ -22,7 +22,10 @@ import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance.Lifecycle +import org.junit.jupiter.api.fail import org.junit.jupiter.api.io.TempDir +import org.slf4j.Logger +import org.slf4j.LoggerFactory import java.io.File import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -30,12 +33,16 @@ import kotlin.test.assertTrue @TestInstance(Lifecycle.PER_CLASS) abstract class IntegrationTest(private val installJsonFeature: Boolean = true) { + companion object { + private val LOG: Logger = LoggerFactory.getLogger(IntegrationTest::class.java) + } + protected lateinit var testComponent: TestComponent protected val db by lazy { testComponent.getDatabase() } private val lifecycleManager by lazy { testComponent.getLifecycleManager() } protected val setupManager by lazy { testComponent.getSetupManager() } protected val metadataManager by lazy { testComponent.getMetadataManager() } - private val wipeManager by lazy { testComponent.getWipeManager() } + protected val wipeManager by lazy { testComponent.getWipeManager() } protected val httpClient = HttpClient(CIO) { expectSuccess = false // prevents exceptions on non-success responses if (installJsonFeature) { @@ -55,6 +62,18 @@ abstract class IntegrationTest(private val installJsonFeature: Boolean = true) { protected val contact1 = getNewRandomContact() protected val contact2 = getNewRandomContact() + @Volatile + protected var exceptionInBackgroundThread = false + + init { + // Ensure exceptions thrown on worker threads cause tests to fail + val fail = Thread.UncaughtExceptionHandler { _: Thread?, throwable: Throwable -> + LOG.warn("Caught unhandled exception", throwable) + exceptionInBackgroundThread = true + } + Thread.setDefaultUncaughtExceptionHandler(fail) + } + @BeforeAll fun setUp(@TempDir tempDir: File) { testComponent = DaggerTestComponent.builder().testModule(TestModule(tempDir)).build() @@ -71,7 +90,8 @@ abstract class IntegrationTest(private val installJsonFeature: Boolean = true) { } @BeforeEach - open fun initDb() { + open fun beforeEach() { + exceptionInBackgroundThread = false // need to reopen database here because we're closing it after each test db.open(null) db.read { txn -> @@ -82,9 +102,19 @@ abstract class IntegrationTest(private val installJsonFeature: Boolean = true) { } @AfterEach - open fun wipe() { - wipeManager.wipeDatabaseAndFiles() - assertFalse(setupManager.hasDb) + open fun afterEach() { + afterEach(true) + } + + fun afterEach(wipe: Boolean) { + if (wipe) { + wipeManager.wipeDatabaseAndFiles() + assertFalse(setupManager.hasDb) + } + + if (exceptionInBackgroundThread) { + fail("background thread has thrown an exception unexpectedly") + } } protected fun addOwnerToken() { diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/settings/MetadataRouteManagerTest.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/settings/MetadataRouteManagerTest.kt index 726a6f8cbf6cd9ce343b04c04cdc60e5e64de7d4..71fa450b14929c1836a3fe0bab9e5c395f2f776c 100644 --- a/mailbox-core/src/test/java/org/briarproject/mailbox/core/settings/MetadataRouteManagerTest.kt +++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/settings/MetadataRouteManagerTest.kt @@ -13,8 +13,8 @@ import kotlin.test.assertEquals class MetadataRouteManagerTest : IntegrationTest() { @BeforeEach - override fun initDb() { - super.initDb() + override fun beforeEach() { + super.beforeEach() addOwnerToken() addContact(contact1) addContact(contact2) diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipeTest1.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipeTest1.kt index e140ae19892692057770cc517bc2df8d397e7f37..11b79b0fb254374d434b16f888b04d9258222fa0 100644 --- a/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipeTest1.kt +++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipeTest1.kt @@ -8,9 +8,9 @@ import kotlin.concurrent.thread class WipeTest1 : IntegrationTest() { @Test - fun test() { + fun `wiping and concurrent database access doesn't cause deadlocks`() { val t1 = thread(name = "dropper") { - db.dropAllTablesAndClose() + wipeManager.wipeDatabaseAndFiles() } // This most likely throws a DbException within the thread, but if it does is doesn't make // the test fail. @@ -19,13 +19,17 @@ class WipeTest1 : IntegrationTest() { } t1.join() t2.join() + + // reset flag for exceptions thrown on background threads as this can indeed happen here + // and is OK + exceptionInBackgroundThread = false } @AfterEach - override fun clearDb() { - // This is not expected to work because calling dropAllTablesAndClose() on a closed Database + override fun afterEach() { + // We need to pass false here because calling wipeDatabaseAndFiles() with a closed Database // throws a DbClosedException. - // super.clearDb() + afterEach(false) } } diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipeTest2.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipeTest2.kt index d7e0ae452d9eb0f837a310c702497de2e49daba3..14de328da47b88123d7ce5b5d850e10342266fba 100644 --- a/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipeTest2.kt +++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/WipeTest2.kt @@ -22,11 +22,11 @@ class WipeTest2 : IntegrationTest() { } @Test - fun test() { + fun `concurrent wipe requests don't interfere and database access doesn't cause deadlocks`() { // One of the two dropper threads will succeed, the other will fail. val t1 = thread(name = "dropper 1") { try { - db.dropAllTablesAndClose() + wipeManager.wipeDatabaseAndFiles() incrementSuccess() } catch (t: Throwable) { incrementFailure() @@ -34,7 +34,7 @@ class WipeTest2 : IntegrationTest() { } val t2 = thread(name = "dropper 2") { try { - db.dropAllTablesAndClose() + wipeManager.wipeDatabaseAndFiles() incrementSuccess() } catch (t: Throwable) { incrementFailure() @@ -50,13 +50,17 @@ class WipeTest2 : IntegrationTest() { t3.join() assertEquals(1, succeeded) assertEquals(1, failed) + + // reset flag for exceptions thrown on background threads as this can indeed happen here + // and is OK + exceptionInBackgroundThread = false } @AfterEach - override fun clearDb() { - // This is not expected to work because calling dropAllTablesAndClose() on a closed Database + override fun afterEach() { + // We need to pass false here because calling wipeDatabaseAndFiles() with a closed Database // throws a DbClosedException. - // super.clearDb() + afterEach(false) } }