什么原因导致间歇性 Robolectric 异常(警告)未调用显式终止方法“close”

问题描述 投票:0回答:1

我正在为我的 Android 应用程序网络层创建 junit 测试,我间歇性地在测试日志中看到以下消息

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)

此外我还看到了这条消息

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)

这是我的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")
        }
    }
}

这是我用于测试资源的 gradle

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")

我做错了什么? 我需要关心这些消息吗? 有什么办法可以阻止他们吗?解决这个问题吗?

android junit4 robolectric
1个回答
0
投票

有关

SQLiteConnection.close
未调用的警告表示数据库连接正在打开但未正确关闭。这可能会导致测试中出现内存泄漏和资源管理问题。
确保在测试的拆卸过程中关闭所有资源(例如数据库连接)。这可以在测试类中的
@After
带注释的方法
中完成。另请参阅“
System.Data.SQLite Close()
未释放数据库文件

@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
}

有关无法销毁临时目录的错误可能意味着测试运行期间创建的某些文件或资源未正确清理。

如果您正在使用任何需要关闭或释放的自定义资源或组件,请确保它们在测试生命周期中得到正确处理。
Robolectric 为测试运行创建临时目录。确保测试后清理这些目录。有时这可能是测试框架本身的问题,但通常与测试中如何处理资源有关。
如果问题仍然存在,您可能需要手动清理或使用 try-with-resources 语句来确保正确关闭资源。

由于堆栈跟踪包含对

com.google.android.datatransport.runtime
的引用,请确保您使用的任何第三方库与 Robolectric 兼容,并且不会导致资源管理问题。

© www.soinside.com 2019 - 2024. All rights reserved.