Issue
I've generated a native library using ndk-build
which I'm able to load and use with in my Android application. However, I want to write some tests against this part of my app.
When calling the native function in my tests, I recieve this exception message:
java.lang.UnsatisfiedLinkError: no process in java.library.path
...where process
is my native library to import, named libprocess.so
.
I'm using Roboelectric for my tests, and running this particular one with the RobolectricTestRunner
, if it makes a difference.
How can I get my test project to 'see' the native library?
Edit: I'm loading the library in my app like so:
static {
System.loadLibrary("process");
}
public static native int[] process(double[][] data);
calling Process.process(array)
works fine in the app (the library is loaded), but fails when run from the tests with the exception given above.
Edit 2:
If I set -Djava.library.path="<the directory of libprocess.so>"
as a VM argument, then:
System.out.println(System.getProperty("java.library.path"));
does show the path I set, but I still get the same exception. I'm setting the directory as:
<project-name>/libs/x86
...but as an absolute path.
Solution
For anyone still looking, blork had the right idea - you need to compile your native libraries for your 'native' platform (Windows, Linux, Mac). The Android NDK builds libraries for the Android platform (.so files - might also work on Linux), and this is why there are no issues running in Activity Test Cases (because it loads up an Android instance).
To get the low-level, hella fast JUnit tests running, you need to support your JVM. On Windows, this might be building DLLs, on Apple, it's building dylibs (assuming shared libraries).
I've just completed a sample in my android-ndk-swig-example repo (https://github.com/sureshjoshi/android-ndk-swig-example/issues/9).
Basically, in my CMakeLists, I added an Apple caveat:
# Need to create the .dylib and .jnilib files in order to run JUnit tests
if (APPLE)
# Ensure jni.h is found
find_package(JNI REQUIRED)
include_directories(${JAVA_INCLUDE_PATH})
And then I make sure Gradle runs for unit tests, but using the Mac build system (not NDK).
def osxDir = projectDir.absolutePath + '/.externalNativeBuild/cmake/debug/osx/'
task createBuildDir() {
def folder = new File(osxDir)
if (!folder.exists()) {
folder.mkdirs()
}
}
task runCMake(type: Exec) {
dependsOn createBuildDir
workingDir osxDir // Jump to future build directory
commandLine '/usr/local/bin/cmake' // Path from HomeBrew installation
args '../../../../' // Relative path for out-of-source builds
}
task runMake(type: Exec) {
dependsOn runCMake
workingDir osxDir
commandLine 'make'
}
project.afterEvaluate {
// Not sure how much of a hack this is - but it allows CMake/SWIG to run before Android Studio
// complains about missing generated files
// TODO: Probably need a release hook too?
javaPreCompileDebug.dependsOn externalNativeBuildDebug
if (org.gradle.internal.os.OperatingSystem.current().isMacOsX()) {
javaPreCompileDebugAndroidTest.dependsOn runMake
}
}
CAVEAT TIME!!!
When you use this method, you're technically not testing the NDK-generated libs. You're testing the same code, but compiled using a different compiler (msvc, xcode, gcc, clang, whatever you use on the host).
What this means practically, is that most of the test results will be valid - except when you run into problems caused by each compiler's quirks, or STL implementations, etc... This isn't as bad as it was 10+ years ago, but you can't say with 100% certainty that the results of JUnit testing with host libs is identical to the Android libs. You can say that it's reasonably close, though.
Then again, unless you're running your native unit tests using the Android NDK for each supported architecture, you also can't say anything about certainty either... So take what you will from it.
An overkill approach (but really cool if automated) would be to write your native unit tests however you do them (Google Test, Catch, etc), then compile and run your native libs and unit tests with the Android NDK per each architecture. This provides your C/C++ coverage across your potential target architectures.
From here, you could use the aforementioned host libs with JUnit to rapidly unit test your JNI layer interacting with your native lib. In your CI system, you should still probably run these same unit tests - but as Android Instrumentation tests (or something else that runs an emulated Android environment).
As with everything, wherever you have an interface, you can create mocks - but at some point, you'll need system/functional/integration tests too.
Update:
More comprehensive explanation of above in a blog article (http://www.sureshjoshi.com/mobile/android-junit-native-libraries/)
Answered By - SJoshi
Answer Checked By - Marilyn (JavaFixing Volunteer)