ankidroid / ankidroid/Anki-Android

[Bug] Unit Tests: java.lang.UnsatisfiedLinkError: 'byte[][] net.ankiweb.rsdroid.NativeMethods.openBackend(byte[])' when mixing `RunWith(AndroidJUnit4::class)`

Abierto
#14,796 8 comentarios 0 reacciones 0 asignados Ver en GitHub
Bug Keep Open Tests
Lenguaje dominante
Kotlin
Estrellas
11.8k
Forks
2.9k
Merge medio
2 d 3 h
PR fusionados (30 d)
171

Descripción

A mix of `JvmTest` with and without the `AndroidJUnit4` annotation produces flaky tests

## Steps

Apply this patch & run tests on com.ichi2.anki.libanki

```patch
Subject: [PATCH] speed
---
Index: AnkiDroid/src/test/java/com/ichi2/libanki/FlagTest.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/FlagTest.kt b/AnkiDroid/src/test/java/com/ichi2/libanki/FlagTest.kt
--- a/AnkiDroid/src/test/java/com/ichi2/libanki/FlagTest.kt (revision 4ac94b14feb30004eb3d7f68cdb88f075b0fc459)
+++ b/AnkiDroid/src/test/java/com/ichi2/libanki/FlagTest.kt (date 1700607248378)
@@ -21,7 +21,6 @@
import org.junit.Test
import org.junit.runner.RunWith

-@RunWith(AndroidJUnit4::class)
class FlagTest : JvmTest() {
/*****************
** Flags *
Index: AnkiDroid/src/test/java/com/ichi2/libanki/AbstractSchedTest.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/AbstractSchedTest.kt b/AnkiDroid/src/test/java/com/ichi2/libanki/AbstractSchedTest.kt
--- a/AnkiDroid/src/test/java/com/ichi2/libanki/AbstractSchedTest.kt (revision 4ac94b14feb30004eb3d7f68cdb88f075b0fc459)
+++ b/AnkiDroid/src/test/java/com/ichi2/libanki/AbstractSchedTest.kt (date 1700607248355)
@@ -15,7 +15,6 @@
*/
package com.ichi2.libanki

-import androidx.test.ext.junit.runners.AndroidJUnit4
import com.ichi2.libanki.sched.Counts
import com.ichi2.testutils.JvmTest
import com.ichi2.utils.KotlinCleanup
@@ -23,7 +22,6 @@
import org.hamcrest.Matchers.*
import org.json.JSONArray
import org.junit.Test
-import org.junit.runner.RunWith
import kotlin.test.assertEquals
import kotlin.test.assertNotNull

@@ -32,7 +30,6 @@
@KotlinCleanup("is -> equalTo")
@KotlinCleanup("reduce newlines in asserts")
@KotlinCleanup("improve increaseAndAssertNewCountsIs")
-@RunWith(AndroidJUnit4::class)
class AbstractSchedTest : JvmTest() {
@Test
fun ensureUndoCorrectCounts() {
Index: AnkiDroid/build.gradle
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/build.gradle b/AnkiDroid/build.gradle
--- a/AnkiDroid/build.gradle (revision 4ac94b14feb30004eb3d7f68cdb88f075b0fc459)
+++ b/AnkiDroid/build.gradle (date 1700607248350)
@@ -388,6 +388,7 @@
testImplementation("androidx.fragment:fragment-testing:$fragments_version")
// in a JvmTest we need org.json.JSONObject to not be mocked
testImplementation 'org.json:json:20220924'
+ testImplementation 'io.github.ivanshafran:shared-preferences-mock:1.2.4'
// May need a resolution strategy for support libs to our versions
androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version"
androidTestImplementation("androidx.test.espresso:espresso-contrib:$espresso_version") {
Index: AnkiDroid/src/test/java/com/ichi2/testutils/JvmTest.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/test/java/com/ichi2/testutils/JvmTest.kt b/AnkiDroid/src/test/java/com/ichi2/testutils/JvmTest.kt
--- a/AnkiDroid/src/test/java/com/ichi2/testutils/JvmTest.kt (revision 4ac94b14feb30004eb3d7f68cdb88f075b0fc459)
+++ b/AnkiDroid/src/test/java/com/ichi2/testutils/JvmTest.kt (date 1700607248414)
@@ -18,6 +18,10 @@

import android.annotation.SuppressLint
import androidx.annotation.CallSuper
+import androidx.core.content.edit
+import com.github.ivanshafran.sharedpreferencesmock.SPMockBuilder
+import com.ichi2.anki.AnkiDroidApp
+import com.ichi2.anki.CollectionHelper
import com.ichi2.anki.CollectionManager
import com.ichi2.libanki.ChangeManager
import com.ichi2.libanki.Collection
@@ -38,6 +42,7 @@
import org.junit.Before
import timber.log.Timber
import timber.log.Timber.Forest.plant
+import java.nio.file.Files

open class JvmTest {
private fun maybeSetupBackend() {
@@ -57,6 +62,12 @@
@Before
@CallSuper
open fun setUp() {
+ AnkiDroidApp.sharedPreferencesTestingOverride = SPMockBuilder().createSharedPreferences()
+ AnkiDroidApp.sharedPrefs().edit {
+ putString(CollectionHelper.PREF_COLLECTION_PATH, Files.createTempDirectory(
+ "AnkiDroid-JvmTest"
+ ).toFile().path)
+ }
TimeManager.resetWith(MockTime(2020, 7, 7, 7, 0, 0, 0, 10))

ChangeManager.clearSubscribers()
@@ -83,6 +94,7 @@
@After
@CallSuper
open fun tearDown() {
+ AnkiDroidApp.sharedPreferencesTestingOverride = null
try {
// If you don't tear down the database you'll get unexpected IllegalStateExceptions related to connections
col_?.close()
Index: AnkiDroid/src/test/java/com/ichi2/libanki/DecksTest.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/DecksTest.kt b/AnkiDroid/src/test/java/com/ichi2/libanki/DecksTest.kt
--- a/AnkiDroid/src/test/java/com/ichi2/libanki/DecksTest.kt (revision 4ac94b14feb30004eb3d7f68cdb88f075b0fc459)
+++ b/AnkiDroid/src/test/java/com/ichi2/libanki/DecksTest.kt (date 1700607248370)
@@ -30,7 +30,6 @@
import kotlin.test.assertFalse
import kotlin.test.assertTrue

-@RunWith(AndroidJUnit4::class)
class DecksTest : JvmTest() {
@Test
fun test_remove() {
Index: AnkiDroid/src/test/java/com/ichi2/libanki/PythonExtensionsTest.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/PythonExtensionsTest.kt b/AnkiDroid/src/test/java/com/ichi2/libanki/PythonExtensionsTest.kt
--- a/AnkiDroid/src/test/java/com/ichi2/libanki/PythonExtensionsTest.kt (revision 4ac94b14feb30004eb3d7f68cdb88f075b0fc459)
+++ b/AnkiDroid/src/test/java/com/ichi2/libanki/PythonExtensionsTest.kt (date 1700607248400)
@@ -27,7 +27,6 @@
import org.junit.Test
import org.junit.runner.RunWith

-@RunWith(AndroidJUnit4::class)
class PythonExtensionsTest {

@Test
Index: AnkiDroid/src/test/java/com/ichi2/libanki/ModelTest.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/ModelTest.kt b/AnkiDroid/src/test/java/com/ichi2/libanki/ModelTest.kt
--- a/AnkiDroid/src/test/java/com/ichi2/libanki/ModelTest.kt (revision 4ac94b14feb30004eb3d7f68cdb88f075b0fc459)
+++ b/AnkiDroid/src/test/java/com/ichi2/libanki/ModelTest.kt (date 1700607248392)
@@ -37,7 +37,6 @@
return " data-cloze=\"${data}\""
}

-@RunWith(AndroidJUnit4::class)
@KotlinCleanup("improve kotlin code where possible")
class NotetypeTest : JvmTest() {
@Test
Index: AnkiDroid/src/test/java/com/ichi2/libanki/UtilsTest.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/UtilsTest.kt b/AnkiDroid/src/test/java/com/ichi2/libanki/UtilsTest.kt
--- a/AnkiDroid/src/test/java/com/ichi2/libanki/UtilsTest.kt (revision 4ac94b14feb30004eb3d7f68cdb88f075b0fc459)
+++ b/AnkiDroid/src/test/java/com/ichi2/libanki/UtilsTest.kt (date 1700607248411)
@@ -23,7 +23,6 @@
import org.junit.runner.RunWith
import java.util.*

-@RunWith(AndroidJUnit4::class)
class UtilsTest {

@Test
Index: AnkiDroid/src/test/java/com/ichi2/libanki/FinderTest.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/FinderTest.kt b/AnkiDroid/src/test/java/com/ichi2/libanki/FinderTest.kt
--- a/AnkiDroid/src/test/java/com/ichi2/libanki/FinderTest.kt (revision 4ac94b14feb30004eb3d7f68cdb88f075b0fc459)
+++ b/AnkiDroid/src/test/java/com/ichi2/libanki/FinderTest.kt (date 1700607248374)
@@ -36,7 +36,6 @@
import timber.log.Timber
import java.util.*

-@RunWith(AndroidJUnit4::class)
class FinderTest : JvmTest() {
@Test
@Config(qualifiers = "en")
Index: AnkiDroid/src/test/java/com/ichi2/libanki/MathJaxClozeTest.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/MathJaxClozeTest.kt b/AnkiDroid/src/test/java/com/ichi2/libanki/MathJaxClozeTest.kt
--- a/AnkiDroid/src/test/java/com/ichi2/libanki/MathJaxClozeTest.kt (revision 4ac94b14feb30004eb3d7f68cdb88f075b0fc459)
+++ b/AnkiDroid/src/test/java/com/ichi2/libanki/MathJaxClozeTest.kt (date 1700607248382)
@@ -12,7 +12,6 @@
import org.junit.Test
import org.junit.runner.RunWith

-@RunWith(AndroidJUnit4::class)
@KotlinCleanup("removeFormattingFromMathjax was imported to stop bug in Kotlin: java.lang.NoSuchFieldError: INSTANCE")
@KotlinCleanup("add testing function returning c.models.byName(\"Cloze\")")
class MathJaxClozeTest : JvmTest() {
Index: AnkiDroid/src/test/java/com/ichi2/libanki/CardTest.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/CardTest.kt b/AnkiDroid/src/test/java/com/ichi2/libanki/CardTest.kt
--- a/AnkiDroid/src/test/java/com/ichi2/libanki/CardTest.kt (revision 4ac94b14feb30004eb3d7f68cdb88f075b0fc459)
+++ b/AnkiDroid/src/test/java/com/ichi2/libanki/CardTest.kt (date 1700607248359)
@@ -31,7 +31,7 @@
import java.util.*
import kotlin.test.assertNotNull

-@RunWith(AndroidJUnit4::class)
+@RunWith(AndroidJUnit4::class) // nextDueTest: Short Date Format is different
class CardTest : JvmTest() {

@Test
Index: AnkiDroid/src/test/java/com/ichi2/libanki/TagsTest.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/TagsTest.kt b/AnkiDroid/src/test/java/com/ichi2/libanki/TagsTest.kt
--- a/AnkiDroid/src/test/java/com/ichi2/libanki/TagsTest.kt (revision 4ac94b14feb30004eb3d7f68cdb88f075b0fc459)
+++ b/AnkiDroid/src/test/java/com/ichi2/libanki/TagsTest.kt (date 1700607248407)
@@ -22,7 +22,6 @@
import org.junit.Test
import org.junit.runner.RunWith

-@RunWith(AndroidJUnit4::class)
class TagsTest : JvmTest() {

@Test
Index: AnkiDroid/src/test/java/com/ichi2/libanki/StorageRustTest.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/StorageRustTest.kt b/AnkiDroid/src/test/java/com/ichi2/libanki/StorageRustTest.kt
--- a/AnkiDroid/src/test/java/com/ichi2/libanki/StorageRustTest.kt (revision 4ac94b14feb30004eb3d7f68cdb88f075b0fc459)
+++ b/AnkiDroid/src/test/java/com/ichi2/libanki/StorageRustTest.kt (date 1700607248403)
@@ -24,7 +24,6 @@
import org.junit.runner.RunWith
import org.robolectric.annotation.Config

-@RunWith(AndroidJUnit4::class)
class StorageRustTest : JvmTest() {

@Test
Index: AnkiDroid/src/test/java/com/ichi2/libanki/ConfigTest.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/ConfigTest.kt b/AnkiDroid/src/test/java/com/ichi2/libanki/ConfigTest.kt
--- a/AnkiDroid/src/test/java/com/ichi2/libanki/ConfigTest.kt (revision 4ac94b14feb30004eb3d7f68cdb88f075b0fc459)
+++ b/AnkiDroid/src/test/java/com/ichi2/libanki/ConfigTest.kt (date 1700607248367)
@@ -26,8 +26,6 @@
import org.junit.Test
import org.junit.jupiter.api.assertThrows
import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
class ConfigTest : JvmTest() {

@Test
Index: AnkiDroid/src/test/java/com/ichi2/libanki/CollectionTest.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/CollectionTest.kt b/AnkiDroid/src/test/java/com/ichi2/libanki/CollectionTest.kt
--- a/AnkiDroid/src/test/java/com/ichi2/libanki/CollectionTest.kt (revision 4ac94b14feb30004eb3d7f68cdb88f075b0fc459)
+++ b/AnkiDroid/src/test/java/com/ichi2/libanki/CollectionTest.kt (date 1700607248363)
@@ -26,7 +26,7 @@
import org.junit.runner.RunWith
import java.util.*

-@RunWith(AndroidJUnit4::class)
+@RunWith(AndroidJUnit4::class) // test_timestamps: AnkiDroidApp.instance
class CollectionTest : JvmTest() {
@Test
fun editClozeGenerateCardsInSameDeck() {
Index: AnkiDroid/src/test/java/com/ichi2/libanki/NoteWithColTest.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/NoteWithColTest.kt b/AnkiDroid/src/test/java/com/ichi2/libanki/NoteWithColTest.kt
--- a/AnkiDroid/src/test/java/com/ichi2/libanki/NoteWithColTest.kt (revision 4ac94b14feb30004eb3d7f68cdb88f075b0fc459)
+++ b/AnkiDroid/src/test/java/com/ichi2/libanki/NoteWithColTest.kt (date 1700607248395)
@@ -24,7 +24,6 @@
import org.junit.runner.RunWith
import org.robolectric.annotation.Config

-@RunWith(AndroidJUnit4::class)
class NoteWithColTest : JvmTest() {

@Test
Index: AnkiDroid/src/test/java/com/ichi2/libanki/MetaTest.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/MetaTest.kt b/AnkiDroid/src/test/java/com/ichi2/libanki/MetaTest.kt
--- a/AnkiDroid/src/test/java/com/ichi2/libanki/MetaTest.kt (revision 4ac94b14feb30004eb3d7f68cdb88f075b0fc459)
+++ b/AnkiDroid/src/test/java/com/ichi2/libanki/MetaTest.kt (date 1700607248387)
@@ -22,7 +22,6 @@
import org.junit.Test
import org.junit.runner.RunWith

-@RunWith(AndroidJUnit4::class)
class MetaTest : JvmTest() {
@Test
fun ensureDatabaseIsInMemory() {
```

## Result

Flaky tests sometimes failing with

```
'byte[][] net.ankiweb.rsdroid.NativeMethods.openBackend(byte[])'
java.lang.UnsatisfiedLinkError: 'byte[][] net.ankiweb.rsdroid.NativeMethods.openBackend(byte[])'
at net.ankiweb.rsdroid.NativeMethods.openBackend(Native Method)
at net.ankiweb.rsdroid.Backend.(Backend.kt:81)
```

This is not a failure to call `RustBackendLoader.ensureSetup()`, if `ensureSetup()` is forced to be called twice, it fails `Native Library /private/var/folders/ym/nqynp93d4j74dpzw20sq4wq00000gn/T/librsdroid-b7f0b01dd98e063d7f3e09f8a0a0979aafe9dc0c.dylib already loaded in another classloader`

### Classloaders

There are two classloaders in play:

* `jdk.internal.loader.ClassLoaders$AppClassLoader@14899482` (`currentThread on JvmTest/ClassLoader.getSystemClassLoader()`)
* `org.robolectric.internal.bytecode.SandboxClassLoader` (`currentThread on @RunWith(AndroidJUnit4::class)`)

Inside a JvmTest, if you use the non-default (`SandboxClassLoader`) to load `RustBackendLoader`:
* First test passes, second test fails with `java.lang.UnsatisfiedLinkError: 'byte[][] ...`

Inside an AndroidJUnitTest: if you use the non-default `AppClassLoader`:
* First test fails with `java.lang.UnsatisfiedLinkError: 'byte[][] ...`

###### Debug info
HEAD is 4ac94b14feb30004eb3d7f68cdb88f075b0fc459

###### Research
- [x] I have read the [support page](https://ankidroid.org/docs/help.html) and am reporting a bug or enhancement request specific to AnkiDroid
- [x] I have checked the [manual](https://ankidroid.org/docs/manual.html) and the [FAQ](https://github.com/ankidroid/Anki-Android/wiki/FAQ) and could not find a solution to my issue
- [x] I have searched for similar existing issues here and on the user forum
- [x] (Optional) I have confirmed the issue is not resolved in the latest alpha release ([instructions](https://docs.ankidroid.org/manual.html#betaTesting))

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.