14

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?
tonyamjr
  • 11
  • 6
ShiraOzeri
  • 291
  • 4
  • 14

7 Answers7

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)
Alex Cohn
  • 56,089
  • 9
  • 113
  • 307
  • 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? – ShiraOzeri Sep 28 '17 at 08:06
  • 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. – Alex Cohn Sep 28 '17 at 08:31
  • 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. – ShiraOzeri Sep 28 '17 at 13:54
  • In my script, the file is called `footest`; it can be found in `app/.externalNativeBuild/cmake/debug/x86/` – Alex Cohn Sep 28 '17 at 15:31
  • 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. – ShiraOzeri Sep 28 '17 at 15:45
  • 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. – Alex Cohn Sep 28 '17 at 15:53
  • 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. – ShiraOzeri Oct 01 '17 at 12:50
  • nonono. Let's continue in [chat](https://chat.stackoverflow.com/rooms/155698/room-for-alex-cohn-and-shiraozeri) – Alex Cohn Oct 01 '17 at 12:58
  • Is it possible to run the tests on Windows instead of the emulator? – rozina Apr 09 '19 at 13:18
  • @rozina 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… – Alex Cohn Apr 10 '19 at 09:05
  • @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. – rozina Apr 10 '19 at 10:51
  • Android Studio is a variant of the very versatile IntelliJ IDE, you probably can find plugins that will do the job. – Alex Cohn Apr 10 '19 at 14:40
  • 1
    @rozina you may find this discussion interesting: https://github.com/android-ndk/ndk/issues/500 – Alex Cohn Apr 12 '19 at 16:02
  • 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. – drlolly Oct 17 '19 at 15:56
  • permission denied. – Homan Huang Apr 02 '21 at 00:41
  • @HomanHuang you are welcome to provide more details, either as a new question here, or a new issue in https://github.com/android-ndk/ndk/issues – Alex Cohn Apr 02 '21 at 16:13
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.

donturner
  • 17,867
  • 8
  • 59
  • 81
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.

Jeremy Jao
  • 371
  • 3
  • 8
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\")
Mr. Goldberg
  • 101
  • 9
  • 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 denied – Homan Huang Apr 02 '21 at 00:44
  • native-tests-lib permission: -rw-rw-rw- . Where is the X? – Homan Huang Apr 02 '21 at 00:57
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

Dan Albert
  • 10,079
  • 2
  • 36
  • 79
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"
        }
    }

}
drlolly
  • 157
  • 2
  • 6
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)
beginner
  • 2,366
  • 4
  • 29
  • 53