What causes intermittent Robolectric exception (warning) Explicit termination method 'close' not called

130 views Asked by At

I am creating junit tests for my android application network layer and intermittently i see the following message in my test logs

System.logW: A resource was acquired at attached stack trace but never released. See java.io.Closeable for information on avoiding resource leaks.
java.lang.Throwable: Explicit termination method 'SQLiteConnection.close' not called
    at dalvik.system.CloseGuard.$$robo$$dalvik_system_CloseGuard$openWithCallSite(CloseGuard.java:288)
    at dalvik.system.CloseGuard.openWithCallSite(CloseGuard.java)
    at dalvik.system.CloseGuard.$$robo$$dalvik_system_CloseGuard$open(CloseGuard.java:257)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.base/java.lang.reflect.Method.invoke(Unknown Source)
    at org.robolectric.shadows.ShadowCloseGuard$CloseGuardReflector$$Reflector289.open(Unknown Source)
    at org.robolectric.shadows.ShadowCloseGuard.open(ShadowCloseGuard.java:38)
    at dalvik.system.CloseGuard.open(CloseGuard.java)
    at android.database.sqlite.SQLiteConnection.$$robo$$android_database_sqlite_SQLiteConnection$__constructor__(SQLiteConnection.java:182)
    at android.database.sqlite.SQLiteConnection.<init>(SQLiteConnection.java)
    at android.database.sqlite.SQLiteConnection.$$robo$$android_database_sqlite_SQLiteConnection$open(SQLiteConnection.java:202)
    at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java)
    at android.database.sqlite.SQLiteConnectionPool.$$robo$$android_database_sqlite_SQLiteConnectionPool$openConnectionLocked(SQLiteConnectionPool.java:512)
    at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java)
    at android.database.sqlite.SQLiteConnectionPool.$$robo$$android_database_sqlite_SQLiteConnectionPool$open(SQLiteConnectionPool.java:210)
    at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java)
    at android.database.sqlite.SQLiteConnectionPool.$$robo$$android_database_sqlite_SQLiteConnectionPool$open(SQLiteConnectionPool.java:202)
    at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java)
    at android.database.sqlite.SQLiteDatabase.$$robo$$android_database_sqlite_SQLiteDatabase$openInner(SQLiteDatabase.java:1085)
    at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java)
    at android.database.sqlite.SQLiteDatabase.$$robo$$android_database_sqlite_SQLiteDatabase$open(SQLiteDatabase.java:1065)
    at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java)
    at android.database.sqlite.SQLiteDatabase.$$robo$$android_database_sqlite_SQLiteDatabase$openDatabase(SQLiteDatabase.java:929)
    at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java)
    at android.database.sqlite.SQLiteDatabase.$$robo$$android_database_sqlite_SQLiteDatabase$openDatabase(SQLiteDatabase.java:918)
    at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java)
    at android.database.sqlite.SQLiteOpenHelper.$$robo$$android_database_sqlite_SQLiteOpenHelper$getDatabaseLocked(SQLiteOpenHelper.java:373)
    at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java)
    at android.database.sqlite.SQLiteOpenHelper.$$robo$$android_database_sqlite_SQLiteOpenHelper$getWritableDatabase(SQLiteOpenHelper.java:316)
    at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java)
    at com.google.android.datatransport.runtime.scheduling.persistence.SQLiteEventStore.retryIfDbLocked(SQLiteEventStore.java:582)
    at com.google.android.datatransport.runtime.scheduling.persistence.SQLiteEventStore.getDb(SQLiteEventStore.java:95)
    at com.google.android.datatransport.runtime.scheduling.persistence.SQLiteEventStore.runCriticalSection(SQLiteEventStore.java:765)
    at com.google.android.datatransport.runtime.scheduling.jobscheduling.WorkInitializer.lambda$ensureContextsScheduled$1(WorkInitializer.java:54)
    at com.google.android.datatransport.runtime.SafeLoggingExecutor$SafeLoggingRunnable.run(SafeLoggingExecutor.java:47)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.base/java.lang.Thread.run(Unknown Source)

and in addition i am seeing this message

ERROR: Failed to destroy temp directory
java.nio.file.DirectoryNotEmptyException: /var/folders/pr/pzzm0m_17273s8dcc2fw7b6w0000gn/T/robolectric-nativeruntime16490046703417862469/fonts
    at java.base/sun.nio.fs.UnixFileSystemProvider.implDelete(Unknown Source)
    at java.base/sun.nio.fs.AbstractFileSystemProvider.delete(Unknown Source)
    at java.base/java.nio.file.Files.delete(Unknown Source)
    at org.robolectric.util.TempDirectory$1.postVisitDirectory(TempDirectory.java:129)
    at org.robolectric.util.TempDirectory$1.postVisitDirectory(TempDirectory.java:119)
    at java.base/java.nio.file.Files.walkFileTree(Unknown Source)
    at java.base/java.nio.file.Files.walkFileTree(Unknown Source)
    at org.robolectric.util.TempDirectory.clearDirectory(TempDirectory.java:119)
    at org.robolectric.util.TempDirectory.destroy(TempDirectory.java:111)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.base/java.lang.Thread.run(Unknown Source)

heres my junit

import androidx.test.core.app.ApplicationProvider
import com.google.firebase.FirebaseApp
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.engine.mock.MockEngine
import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.defaultRequest
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
import io.ktor.client.plugins.observer.ResponseObserver
import io.ktor.client.request.header
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.serialization.kotlinx.json.json
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.middle.earth.lotr.BuildConfig
import org.middle.earth.lotr.data.remote.dto.character.CharacterResponse
import org.middle.earth.lotr.di.NETWORK_TIME_OUT
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@RunWith(RobolectricTestRunner::class)
@Config(minSdk = 26, maxSdk = 34)
class TheOneApiNetworkTest {

    @Before
    fun setUp() {
        FirebaseApp.initializeApp(ApplicationProvider.getApplicationContext())
    }

    @Test
    fun character() {
        val httpClient = getHttpClient(mockEngineCharactersOk)

        runBlocking {
            val response = TheOneApiHttpService(httpClient).character(1)
            val characters = response.body<CharacterResponse>()
            assertEquals(characters.characters.isNotEmpty(), true)
        }

    }

    @Test(expected = ClientRequestException::class)
    fun characterBadRequest() {
        val httpClient = getHttpClient(mockEngineBadRequest)

        runBlocking {
            TheOneApiHttpService(httpClient).character(1)
        }
    }

    private fun getHttpClient(mockEngine: MockEngine) = HttpClient(mockEngine) {

        expectSuccess = true

        install(ContentNegotiation) {
            json(
                Json {
                    prettyPrint = true
                    isLenient = true
                    useAlternativeNames = true
                    ignoreUnknownKeys = false
                    encodeDefaults = true
                }
            )
        }

        install(HttpTimeout) {
            requestTimeoutMillis = NETWORK_TIME_OUT
            connectTimeoutMillis = NETWORK_TIME_OUT
            socketTimeoutMillis = NETWORK_TIME_OUT
        }

        install(Logging) {
            logger = object : Logger {
                override fun log(message: String) {
                    println("log() called with: message = $message")
                }
            }
            level = LogLevel.ALL
        }

        install(ResponseObserver) {
            onResponse { response ->
                println("providesHttpClient() called with: response = $response\n${response.status.value}")
            }
        }

        defaultRequest {
            header(HttpHeaders.ContentType, ContentType.Application.Json)
            header(HttpHeaders.Authorization, "Bearer ${BuildConfig.THE_ONE_API_ACCESS_TOKEN}")
            header(HttpHeaders.UserAgent, "Android Mock User Agent")
        }
    }
}

heres my gradle for testing resources

testImplementation("junit:junit:4.13.2")
testImplementation("org.slf4j:slf4j-api:2.0.11")
testImplementation("org.slf4j:slf4j-simple:2.0.11")
testImplementation("androidx.test:core:1.5.0")
testImplementation("io.ktor:ktor-client-mock:2.3.7")
testImplementation("io.mockk:mockk:1.13.9")
testImplementation("org.robolectric:robolectric:4.11.1")

what am i doing wrong? do i need to be concerned about these messages? is there any way to stop them. resolve this?

1

There are 1 answers

0
VonC On BEST ANSWER

The warning about SQLiteConnection.close not being called indicates that a database connection is being opened but not properly closed. That can lead to memory leaks and resource management issues in your tests.
Make sure all resources like database connections are closed in the teardown process of your tests. That can be done in an @After annotated method in your test class. See also "System.Data.SQLite Close() not releasing database file"

@RunWith(RobolectricTestRunner.class)
@Config(minSdk = 26, maxSdk = 34)
class TheOneApiNetworkTest {

    // 

    @After
    public void tearDown() {
        // Close any open resources
        // For example, if you have a database helper or connection, close it here
    }

    // Existing test methods
}

The error regarding the failure to destroy the temp directory could mean that some files or resources created during the test run are not being cleaned up properly.

If you are using any custom resources or components that need to be closed or released, make sure they are properly handled in the test lifecycle.
Robolectric creates temporary directories for test runs. Make sure these directories are being cleaned up after the tests. That can be sometimes an issue with the test framework itself, but often it is related to how resources are handled in your tests.
If the issue persists, you might need to manually clean up or use a try-with-resources statement to make sure proper closure of resources.

Since the stack trace includes references to com.google.android.datatransport.runtime, make sure any third-party libraries you are using are compatible with Robolectric and are not causing the resource management issues.