Android NDK with Google Test
Asked Answered
S

7

16

I'm trying to use GoogleTest on Android Studio.

According to what I understood, the latest version of NDK has the gtest included.

I did not find a clear guide how to do it.

I followed this document:

So, I opened a new project, created jni folder and the following files (inside the files I wrote exactly what the document):

enter image description here

But it does not recognize the #include gtest/gtest.h

In addition,

  • how to run the adb at the end?
  • I created an android.mk file but where should I call it?
Stanza answered 26/9, 2017 at 15:3 Comment(0)
V
21

If you choose cmake to drive your externalNativeBuild (and this is the preferred option, according to Android Developers NDK guide), then you can simply add the following lines to your CMakeLists.txt:

set(GOOGLETEST_ROOT ${ANDROID_NDK}/sources/third_party/googletest/googletest)
add_library(gtest STATIC ${GOOGLETEST_ROOT}/src/gtest_main.cc ${GOOGLETEST_ROOT}/src/gtest-all.cc)
target_include_directories(gtest PRIVATE ${GOOGLETEST_ROOT})
target_include_directories(gtest PUBLIC ${GOOGLETEST_ROOT}/include)

add_executable(footest src/main/jni/foo_unittest.cc)
target_link_libraries(footest gtest)

If your build succeeds, you will find app/.externalNativeBuild/cmake/debug/x86/footest. From here, you can follow the instructions in README.NDK to run it on emulator or device.


Notes:

  • make sure that the ABI matches the target you use (the guide is not very clear about this).
  • the list of ABI's that are built is controlled by abiFilters in build.gradle. In Android Studio, even ndk-build ignores APP_ABI set in Application.mk.
  • the files Android.mk and Application.mk are ignored when you use cmake.
  • for gradle-3.3, and classpath 'com.android.tools.build:gradle:2.3.3', as in the current Android Studio release 2.3.3, you may need to explicitly specify the unittest target in build.gradle:

    android { defaultConfig { externalNativeBuild { cmake { targets "foo_unittest" }}}}
    
  • with Android Studio 3.0, gradle-4.1, and classpath 'com.android.tools.build:gradle:3.0.0-beta6' the executable is easier to find under app/build/intermediates/cmake/debug/obj.


To test the foo(int x, int y) function from foo.cpp in a shared library (to make is as close as possible to the NDK instructions), you need some more lines in your CMakeLists.txt script:

# build libfoo.so
add_library(foo SHARED src/main/jni/foo.cpp)
target_link_libraries(footest foo) 

You will find libfoo.so to copy manually to your device under app/build/intermediates/cmake/debug/obj.

To reduce the hassle, you can use STATIC instead of SHARED, or simply add foo.cpp to footest executable:

add_executable(footest src/main/jni/foo_unittest.cc src/main/jni/foo.cpp)
Viator answered 27/9, 2017 at 12:58 Comment(16)
Thanks! You helped me a lot! The build does succeed, but the path I find is:\app\.externalNativeBuild\cmake\debug\x86\CMakeFiles\footest.dir. and I dont have the libfoo.so file. I'm really sorry but from here I do not know what to do..Can I get more help from you please?Stanza
Fair enough, if you don't build libfoo.so, it won't just appear magically. To test the foo(int x, int y) function from foo.cpp, you need some more lines in your CMakeLists.txt script. I add these to the answer above.Viator
A lot of thanks! Well, libfoo.so build .But in the README wrote that both files, 'libfoo.so' and 'foo_unittest' should appear, and I can not find the 'foo_unittest' file- where the tests are located. I tried adding the file as you were adding the libfoo.so but I did not succeed.Stanza
In my script, the file is called footest; it can be found in app/.externalNativeBuild/cmake/debug/x86/Viator
Ok, I wrote your script. And my 'footet' is a folder, not a file. in the folder footest there is src\main\jni and jni are empty.Stanza
Let us make it easier: try to add add_executable(foo_unittest src/main/jni/foo_unittest.cc)\n target_link_libraries(foo_unittest foo gtest) to the end of CMakeLists.txt, run 'Make Project" and check what changes.Viator
The change is: added folder foo_unittest , like the footest folder- in this folder: src/main/jni and jni is a empty folder. Still can not find the file foo_unittest (Which should be run with the adb).thank you again for all the help.Stanza
nonono. Let's continue in chatViator
Is it possible to run the tests on Windows instead of the emulator?Spieler
@Spieler unfortunately, you can not. You can use cmake to build unit tests for Windows from your C++ sources, but it will use a different compiler, different runtime libraries, different STL…Viator
@AlexCohn I don't mind all those things being different (I already build tests on Windows platform using Visual Studio), as long as I can use the same IDE for building production code and running tests. Currently I use a different IDE for tests, which makes it hard to refactor code.Spieler
Android Studio is a variant of the very versatile IntelliJ IDE, you probably can find plugins that will do the job.Viator
@Spieler you may find this discussion interesting: github.com/android-ndk/ndk/issues/500Viator
This still worked oct 2019. Note. You must do the build from within android studio. I am on ubuntu 18-04.. Did not have to worry about specifying abi. It gave 4 abis, armeabi, arm64,x86-64,x86. Running cmake outside studio will not give you any cross-compilation. Only a build for the platform you are running on.Voroshilovgrad
permission denied.Supereminent
@HomanHuang you are welcome to provide more details, either as a new question here, or a new issue in github.com/android-ndk/ndk/issuesViator
G
7

Just to add to Alex's excellent answer, you can also deploy and run the resulting test binary using adb by adding the following to your CMakeLists.txt:

find_program(ADB adb)
add_custom_command(TARGET footest POST_BUILD
    COMMAND ${ADB} shell mkdir -p /data/local/tmp/${ANDROID_ABI}
    COMMAND ${ADB} push $<TARGET_FILE:native-lib> /data/local/tmp/${ANDROID_ABI}/
    COMMAND ${ADB} push $<TARGET_FILE:footest> /data/local/tmp/${ANDROID_ABI}/
    COMMAND ${ADB} shell \"export LD_LIBRARY_PATH=/data/local/tmp/${ANDROID_ABI}\; /data/local/tmp/${ANDROID_ABI}/footest\")

Note that in the above example footest is dependent on the shared library native-lib which is why we push that. The path to native-lib is specified by setting the LD_LIBRARY_PATH environment variable.

Garnishment answered 5/10, 2017 at 12:18 Comment(0)
M
4

To piggyback everyone's answers... Not all the solutions here worked 100%, but I did combine all the answers here to get something that worked for me. I'm building our libraries in CMake, whose build is generated by the Android Studio plugin. I've gotten our GoogleTests running directly via bash and adb.

Caveats:

  • The googletest official documentation essentially gave me a working version for all the platforms we compile. Very trivial! I had to add the args that the Android Gradle plugin uses cross-compile for Android. I used this method since our tests require gmock. The NDK doesn't have it (much wow), so I ended up using the official instructions.
  • Your unit tests are executables, so in your CMakeLists.txt, you must create it using add_executable(UnitTest "") and link your stuff there.
  • Like everyone has said, ${AS_STUDIO_LIBRARY_ROOT}/build/intermediates/cmake/${release|debug}/obj/${ARCH} houses your compiled source. This should include shared libraries and other libs as well as the unit test executable. This executable won't make it to your final APK, so no worries there.
  • Prevent file permission issues by doing the following below. Copying everything to /data/local/tmp/<PROJECT_NAME> directly then chmod 777ing everything will not work for some reason, especially on the Pixel 2 and the emulator:
    1. adb pushing your resources, libraries, and googletest executable to the /sdcard/<PROJECT_NAME> folder first
    2. adb shell mv /sdcard/<PROJECT_NAME> /data/local/tmp/.
    3. chmod 777 -R /data/local/tmp/<PROJECT_NAME>

After this is all done, you should be able to run your googletest like this:

adb shell LD_LIBRARY_PATH=/data/local/tmp/<PROJECT_NAME>; cd /data/local/tmp/<PROJECT_NAME>; ./<GOOGLE_TEST_EXECUTABLE>

I also got remote debugging working via gdbserver and gdb through Visual Studio Code. I'd prefer to use lldb instead but I haven't figured it out yet. This topic to get full debugging to work will require multiple paragraphs, so feel free to PM me if you got lldb working with Visual Studio Code or are curious how I solved this issue.

Don't forget to remove the files after running the unit tests since they'll stay on your device otherwise.

Mucilage answered 21/9, 2018 at 17:59 Comment(0)
A
3

Following Alex Cohn and donturner answers, here is the complete solution to build tests and run them as post-native-build event. Add this (with some changes to filenames/variables) at the end of your CMakeLists.txt.
I've created a sample project with more detailed explanation: https://github.com/Mr-Goldberg/android-studio-googletest

# Build and link tests

set(GTEST_DIR ${ANDROID_NDK}/sources/third_party/googletest) # GTest included into NDK package. You may change to another distribution.

add_library(gtest STATIC ${GTEST_DIR}/src/gtest_main.cc ${GTEST_DIR}/src/gtest-all.cc)
target_include_directories(gtest PRIVATE ${GTEST_DIR})
target_include_directories(gtest PUBLIC ${GTEST_DIR}/include)

add_executable(native-tests-lib ./test/native-libTests.cpp)
target_link_libraries(native-tests-lib native-lib gtest)

# Push and execute tests as post-build event.

set(TARGET_TEST_DIR /data/local/tmp/native-tests-lib/${ANDROID_ABI}) # Directory on device to push tests.

message("ANDROID_SDK_ROOT: ${ANDROID_SDK_ROOT}") # ANDROID_SDK_ROOT should be passed as variable to this script.
find_program(ADB NAMES adb PATHS ${ANDROID_SDK_ROOT}/platform-tools)

add_custom_command(TARGET native-tests-lib POST_BUILD
        COMMAND ${ADB} shell mkdir -p ${TARGET_TEST_DIR}

        # Push libraries

        COMMAND ${ADB} push $<TARGET_FILE:native-tests-lib> ${TARGET_TEST_DIR}/
        COMMAND ${ADB} push $<TARGET_FILE:native-lib> ${TARGET_TEST_DIR}/

        # Execute tests

        COMMAND ${ADB} shell \"export LD_LIBRARY_PATH=${TARGET_TEST_DIR}\; ${TARGET_TEST_DIR}/native-tests-lib\")
Asti answered 4/12, 2019 at 2:51 Comment(2)
adb shell /data/local/tmp/native-tests-lib/x86_64/native-tests-lib /system/bin/sh: /data/local/tmp/native-tests-lib/x86_64/native-tests-lib: can't execute: Permission deniedSupereminent
native-tests-lib permission: -rw-rw-rw- . Where is the X?Supereminent
R
0

You can use CMake or ndk-build (Android.mk), not both. The gtest pieces of the NDK are not plumbed through for CMake. https://github.com/android-ndk/ndk/issues/500

Resonance answered 26/9, 2017 at 22:28 Comment(1)
Thanks! So how do I add what I wrote on Android.mk to CMakeLists.txt?Stanza
V
0

I basically used alex cohns answer. ubuntu 18-04. Must do build from within android studio. Note If run cmake outside stuidio only get build for your current platform. I had to restrict build for armeabi. My target system was this and the static libs I was building with only existed in this form. Since I was linking statically I only had to downloand and run the build target footest. Here is the cmake file, which is basically Alex Cohn's file with additions to link 2 static libs:

# gtest setup
set(ANDROID_NDK /home/labhras/Android/Sdk/ndk/20.0.5594570)
set(GOOGLETEST_ROOT ${ANDROID_NDK}/sources/third_party/googletest)
add_library(gtest STATIC ${GOOGLETEST_ROOT}/src/gtest_main.cc ${GOOGLETEST_ROOT}/src/gtest-all.cc)
target_include_directories(gtest PRIVATE ${GOOGLETEST_ROOT})
target_include_directories(gtest PUBLIC ${GOOGLETEST_ROOT}/include)

# link_directories(~/AndroidStudioProjects/Nativecgtest/app/src/main/cpp)

add_library(libtsl.a STATIC IMPORTED)
set_target_properties(libtsl.a
        PROPERTIES IMPORTED_LOCATION /home/labhras/AndroidStudioProjects/Nativecgtest/app/src/main/cpp/libtsl.a
        INTERFACE_INCLUDE_DIRECTORIES /home/labhras/AndroidStudioProjects/Nativecgtest/app/src/main/cpp/libtsl.a
        )


add_library(libtcl.a STATIC IMPORTED)
set_target_properties(libtcl.a
        PROPERTIES IMPORTED_LOCATION /home/labhras/AndroidStudioProjects/Nativecgtest/app/src/main/cpp/libtcl.a
        INTERFACE_INCLUDE_DIRECTORIES /home/labhras/AndroidStudioProjects/Nativecgtest/app/src/main/cpp/libtcl.a
        )

add_executable(footest  test2.cpp)
target_link_libraries(footest gtest libtsl.a libtcl.a)

I restricted the build to armeabi with the abiFilters line in app build.gradle:

defaultConfig {
    externalNativeBuild {
        cmake {
            abiFilters "armeabi-v7a"
        }
    }

}
Voroshilovgrad answered 22/10, 2019 at 15:37 Comment(0)
T
0

Inspired by Alex Answer

I downloaded the google test from https://github.com/google/googletest and use the following code in my CMakeLists.txt

set(GOOGLETEST_ROOT C:/path/to/folder/googletest)
add_library(gtest STATIC ${GOOGLETEST_ROOT}/src/gtest_main.cc ${GOOGLETEST_ROOT}/src/gtest-all.cc)
target_include_directories(gtest PRIVATE ${GOOGLETEST_ROOT})
target_include_directories(gtest PUBLIC ${GOOGLETEST_ROOT}/include)
target_link_libraries(myExecutable gtest)
Transcend answered 26/3, 2022 at 13:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.