webentwicklung-frage-antwort-db.com.de

Unit-Test-Java-Klasse, die die native Bibliothek lädt

Ich führe Gerätetests in Android Studio aus. Ich habe eine Java-Klasse, die eine native Bibliothek mit dem folgenden Code lädt

 static
    {
       System.loadLibrary("mylibrary");
    }

Aber wenn ich diese Klasse in meinem src/test-Verzeichnis teste, bekomme ich 

Java.lang.UnsatisfiedLinkError: no mylibrary in Java.library.path
    at Java.lang.ClassLoader.loadLibrary(ClassLoader.Java:1864)
    at Java.lang.Runtime.loadLibrary0(Runtime.Java:870)
    at Java.lang.System.loadLibrary(System.Java:1122)

Wie kann ich den Pfad der systemeigenen .so-Bibliotheken finden, die sich unter src/main/libs befinden, um einen Komponententest ohne Fehler durchzuführen?

Hinweis: Im src/main/libs-Verzeichnis habe ich 3 weitere Unterverzeichnisse: armeabi, mips und x86. Jede davon enthält die richtige .so-Datei. Ich verwende die Nicht-experimentelle Version zum Erstellen von NDK-Bibliotheken.

Ich möchte keine anderen Testbibliotheken von Drittanbietern verwenden, da alle anderen "reinen" Java-Klassen gut getestet werden können. Wenn das nicht möglich ist, bin ich offen für Alternativen.

Hier ist mein Testcode, der den Fehler auslöst

   @Test
    public void testNativeClass() throws Exception
    {
        MyNativeJavaClass test = new MyNativeJavaClass("lalalal")
        List<String> results = test.getResultsFromNativeMethodAndPutThemInArrayList();
        assertEquals("There should be only three result", 3, results.size());
    }
41
ThanosFisherman

Die einzige Lösung, die ohne Hacks funktioniert, ist die Verwendung von JUnit durch Testen von Instrumenten (androidTest-Verzeichnis) . Meine Klasse kann jetzt mit dem Android-Gerät oder Emulator getestet werden.

9
ThanosFisherman

Ich bin nicht sicher, ob dies Ihr Problem löst oder nicht, aber bis jetzt hat noch niemand über das Strategiemuster für den Umgang mit Klassen gesprochen, die die Bibliothek während ihrer Erstellung geladen haben.

Schauen wir uns das Beispiel an:

Wir möchten die Fibonacci-Solver-Klasse implementieren. Wenn wir davon ausgehen, dass wir die Implementierung im nativen Code bereitgestellt und die native Bibliothek erstellt haben, können wir Folgendes implementieren:

public interface Fibonacci {
     long calculate(int steps);
}

Erstens stellen wir unsere native Implementierung bereit:

public final class FibonacciNative implements Fibonacci {
    static {
      System.loadLibrary("myfibonacci");
    }

    public native long calculate(int steps);
}

Zweitens bieten wir eine Java-Implementierung für den Fibonacci-Solver:

public final class FibonacciJava implements Fibonacci {

   @Override
   public long calculate(int steps) {
       if(steps > 1) {
           return calculate(steps-2) + calculate(steps-1);
       }
       return steps;
   }
}

Drittens wickeln wir die Solvers mit einer Elternklasse um, die während der Instantiierung ihre eigene Implementierung wählt:

public class FibonnaciSolver implements Fibonacci {

   private static final Fibonacci STRATEGY;

   static {
      Fibonacci implementation;
      try {
         implementation = new FibonnaciNative();
      } catch(Throwable e) {
         implementation = new FibonnaciJava();
      }

      STRATEGY = implementation;
   }

   @Override
   public long calculate(int steps) {
       return STRATEGY.calculate(steps);
   }

}

Daher das Problem beim Auffinden des Pfads zur Bibliothek mit Hilfe der Strategie. Dieser Fall behebt das Problem jedoch nicht, wenn die native Bibliothek wirklich während des Tests einbezogen werden muss. Das Problem wird auch nicht gelöst, wenn die native Bibliothek eine Bibliothek eines Drittanbieters ist.

Grundsätzlich wird das Problem beim Laden der systemeigenen Bibliothek umgangen, indem der native Code für Java-Code verspottet wird.

Hoffe das hilft irgendwie :)

3
dawid gdanski

Es gibt is eine Möglichkeit, den Bibliothekspfad von Gradle-run VM für lokale Komponententests zu konfigurieren. Ich werde es im Folgenden beschreiben, aber Spoiler: In meiner Erfahrung ist @ThanosFisherman richtig: Lokale Unit-Tests für Dinge, die das Android NDK verwenden, scheinen im Moment ein Dummkopf zu sein.

Für alle anderen, die nach einer Möglichkeit suchen, gemeinsam genutzte Bibliotheken (d. H. .so) in Komponententests mit Gradle zu laden, ist hier die etwas langwierige Zusammenfassung:

Ziel ist es, den Pfad der gemeinsam genutzten Bibliothek für die JVM festzulegen, auf der die Komponententests ausgeführt werden.

Obwohl viele Leute vorschlagen, den lib-Pfad in Java.library.path zu setzen, habe ich festgestellt, dass es funktioniert nicht, zumindest nicht auf meinem Linux-Rechner. (auch, gleiche Ergebnisse in diesem CodeRanch-Thread )

Was funktioniert ist, obwohl die Umgebungsvariable LD_LIBRARY_PATH os festgelegt wird (oder PATH ist das engste Synonym in Windows)

Mit Gradle:

// module-level build.gradle
apply plugin: 'com.Android.library' // or application

Android {
    ...

    testOptions {
        unitTests {
            all {
                // This is where we have access to the properties of gradle's Test class,
                // look it  up if you want to customize more test parameters

                // next we take our cmake output dir for whatever architecture
                // you can also put some 3rd party libs here, or override
                // the implicitly linked stuff (libc, libm and others)

                def libpath = '' + projectDir + '/build/intermediates/cmake/debug/obj/x86_64/'
                    +':/home/developer/my-project/some-sdk/lib'

                environment 'LD_LIBRARY_PATH', libpath
            }
        }
    }
}

Damit können Sie z. ./gradlew :mymodule:testDebugUnitTest und die systemeigenen Bibliotheken werden in den angegebenen Pfaden gesucht.

Verwenden des Android Studio-JUnit-Plugins Für das JUnit-Plugin von Android Studio können Sie die Optionen VM und die Umgebungsvariablen in den Einstellungen der Testkonfiguration angeben. Führen Sie einfach einen JUnit-Test aus ( Klicken Sie auf eine Testmethode oder was auch immer) und bearbeiten Sie dann die Konfiguration ausführen:  enter image description here  enter image description here

Obwohl es sich wie "Mission erfüllt" anhört, habe ich festgestellt, dass mir bei Verwendung von libc.so, libm.so und anderen in meinem os /usr/lib Versionsfehler angezeigt werden (wahrscheinlich, weil meine eigene Bibliothek von cmake mit dem Android-ndk-Toolkit gegen die eigenen Plattform-Bibliotheken kompiliert wird). Durch die Verwendung der Plattform-Bibliotheken aus den ndk-Paketen wurde die JVM mit einem SIGSEGV-Fehler heruntergefahren (aufgrund der Inkompatibilität der ndk-Plattform-Bibliotheken mit der Host os-Umgebung).

Update Wie @AlexCohn in den Kommentaren ausführlich darauf hingewiesen hat, muss man gegen die Host-Umgebungsbibliotheken bauen, damit dies funktioniert. Obwohl Ihr Computer höchstwahrscheinlich x86_64 ist, sind die Binärdateien x86_64, die in der NDK-Umgebung erstellt wurden, nicht geeignet.

Es mag etwas sein, was ich natürlich übersehen habe, und ich freue mich über jede Rückmeldung, aber im Moment lasse ich die ganze Idee zugunsten instrumentierter Tests fallen.

1
Ivan Bartsov

Stellen Sie nur sicher, dass das Verzeichnis, in dem sich die Bibliothek befindet, in der Systemeigenschaft Java.library.path enthalten ist.

Im Test konnten Sie es vor dem Laden der Bibliothek einstellen:

System.setProperty("Java.library.path", "... path to the library .../libs/x86");

Sie können den Pfad fest codiert angeben. Dadurch wird das Projekt jedoch weniger in andere Umgebungen portierbar. Ich schlage vor, Sie bauen es programmgesteuert auf.

1
Henry

Versuchen Sie, Ihren Testcode mit der Java -XshowSettings: properties-Option auszuführen, und stellen Sie sicher, dass der Zielpfad für Systembibliotheken festgelegt ist. In der Ausgabe dieses Befehls sind die Bibliothekspfadwerte identisch

0
daemonThread

Die .so-Dateien sollen untergelegt werden 

src/main/jniLibs

Nicht unter src/main/libs

(Getestet mit Android Studio 1.2.2)

Als Referenz können Sie die Seite - http://ph0b.com/Android-studio-gradle-and-ndk-integration/ besuchen, obwohl einige Teile veraltet sein könnten.

0
prabindh

Wenn die Bibliothek für Ihren Test benötigt wird, verwenden Sie einen AndroidTest (unter src/androidTest/...) eher als ein junit Test. Auf diese Weise können Sie die native Bibliothek wie an anderer Stelle im Code laden und verwenden.

Wenn die Bibliothek für Ihren Test nicht benötigt wird, wickeln Sie einfach die Systemlast in einen Versuch/Fang. Auf diese Weise kann die JNI-Klasse weiterhin in Junit-Tests arbeiten (unter src/test/...) und es ist eine sichere Umgehung, da es unwahrscheinlich ist, dass der Fehler maskiert wird (etwas anderes wird sicherlich fehlschlagen, wenn die native lib tatsächlich benötigt wird). Von dort aus können Sie so etwas wie Mockito verwenden, um Methodenaufrufe zu löschen, die die JNI-Bibliothek noch erreichen.

Zum Beispiel in Kotlin:

    companion object {
        init {
            try {
                System.loadLibrary("mylibrary")
            } catch (e: UnsatisfiedLinkError) {
                // log the error or track it in analytics
            }
        }
    }
0
gMale