From d8cc12b36628dc3233e14b1e7e771b195f396e9f Mon Sep 17 00:00:00 2001 From: Alexander Shalamov Date: Thu, 18 Oct 2018 17:30:19 +0300 Subject: [PATCH] [android] Implement android test runner for core unit tests --- Makefile | 22 +++---- platform/android/config.cmake | 18 +++--- platform/android/src/test/Main.java | 15 ----- platform/android/src/test/main.jni.cpp | 76 ----------------------- platform/android/src/test/runtime.cpp | 73 ++++++++++++++++++++++ platform/android/src/test/runtime.hpp | 9 +++ platform/android/src/test/test_runner.cpp | 10 +++ platform/android/src/test/version-script | 8 +++ 8 files changed, 118 insertions(+), 113 deletions(-) delete mode 100644 platform/android/src/test/Main.java delete mode 100644 platform/android/src/test/main.jni.cpp create mode 100644 platform/android/src/test/runtime.cpp create mode 100644 platform/android/src/test/runtime.hpp create mode 100644 platform/android/src/test/test_runner.cpp create mode 100644 platform/android/src/test/version-script diff --git a/Makefile b/Makefile index 8f2c997297b..e8a84718b28 100644 --- a/Makefile +++ b/Makefile @@ -500,7 +500,7 @@ MBGL_ANDROID_ABIS += x86-64;x86_64 MBGL_ANDROID_LOCAL_WORK_DIR = /data/local/tmp/core-tests MBGL_ANDROID_LIBDIR = lib$(if $(filter arm-v8 x86-64,$1),64) MBGL_ANDROID_DALVIKVM = dalvikvm$(if $(filter arm-v8 x86-64,$1),64,32) -MBGL_ANDROID_APK_SUFFIX = $(if $(filter Release,$(BUILDTYPE)),release-unsigned,debug) +MBGL_ANDROID_APK_SUFFIX = $(if $(filter Release,$(BUILDTYPE)),release,debug) MBGL_ANDROID_CORE_TEST_DIR = platform/android/MapboxGLAndroidSDK/.externalNativeBuild/cmake/$(buildtype)/$2/core-tests MBGL_ANDROID_STL ?= c++_static MBGL_ANDROID_GRADLE = ./gradlew --parallel --max-workers=$(JOBS) -Pmapbox.buildtype=$(buildtype) -Pmapbox.stl=$(MBGL_ANDROID_STL) @@ -542,27 +542,21 @@ android-$1: platform/android/gradle/configuration.gradle android-core-test-$1: android-test-lib-$1 # Compile main sources and extract the classes (using the test app to get all transitive dependencies in one place) mkdir -p $(MBGL_ANDROID_CORE_TEST_DIR) - unzip -o platform/android/MapboxGLAndroidSDKTestApp/build/outputs/apk/MapboxGLAndroidSDKTestApp-$(MBGL_ANDROID_APK_SUFFIX).apk classes.dex -d $(MBGL_ANDROID_CORE_TEST_DIR) - - # Compile Test runner - find platform/android/src/test -name "*.java" > $(MBGL_ANDROID_CORE_TEST_DIR)/java-sources.txt - javac -sourcepath platform/android/src/test -d $(MBGL_ANDROID_CORE_TEST_DIR) -source 1.7 -target 1.7 @$(MBGL_ANDROID_CORE_TEST_DIR)/java-sources.txt - - # Combine and dex - cd $(MBGL_ANDROID_CORE_TEST_DIR) && $(ANDROID_HOME)/build-tools/25.0.0/dx --dex --output=test.jar *.class classes.dex + unzip -o platform/android/MapboxGLAndroidSDKTestApp/build/outputs/apk/$(buildtype)/MapboxGLAndroidSDKTestApp-$(MBGL_ANDROID_APK_SUFFIX).apk classes.dex -d $(MBGL_ANDROID_CORE_TEST_DIR) run-android-core-test-$1-%: android-core-test-$1 # Ensure clean state on the device - adb shell "rm -Rf $(MBGL_ANDROID_LOCAL_WORK_DIR) && mkdir -p $(MBGL_ANDROID_LOCAL_WORK_DIR)/test" + adb shell "rm -Rf $(MBGL_ANDROID_LOCAL_WORK_DIR) && mkdir -p $(MBGL_ANDROID_LOCAL_WORK_DIR)/test && mkdir -p $(MBGL_ANDROID_LOCAL_WORK_DIR)/mapbox-gl-js/src/style-spec/reference" # Push all needed files to the device - adb push $(MBGL_ANDROID_CORE_TEST_DIR)/test.jar $(MBGL_ANDROID_LOCAL_WORK_DIR) > /dev/null 2>&1 + adb push $(MBGL_ANDROID_CORE_TEST_DIR)/classes.dex $(MBGL_ANDROID_LOCAL_WORK_DIR) > /dev/null 2>&1 + adb push platform/android/MapboxGLAndroidSDK/build/intermediates/intermediate-jars/$(buildtype)/jni/$2/libmapbox-gl.so $(MBGL_ANDROID_LOCAL_WORK_DIR) > /dev/null 2>&1 adb push test/fixtures $(MBGL_ANDROID_LOCAL_WORK_DIR)/test > /dev/null 2>&1 - adb push platform/android/MapboxGLAndroidSDK/build/intermediates/bundles/default/jni/$2/libmapbox-gl.so $(MBGL_ANDROID_LOCAL_WORK_DIR) > /dev/null 2>&1 - adb push platform/android/MapboxGLAndroidSDK/build/intermediates/bundles/default/jni/$2/libmbgl-test.so $(MBGL_ANDROID_LOCAL_WORK_DIR) > /dev/null 2>&1 + adb push mapbox-gl-js/src/style-spec/reference/v8.json $(MBGL_ANDROID_LOCAL_WORK_DIR)/mapbox-gl-js/src/style-spec/reference > /dev/null 2>&1 + adb push platform/android/MapboxGLAndroidSDK/build/intermediates/cmake/$(buildtype)/obj/$2/mbgl-test $(MBGL_ANDROID_LOCAL_WORK_DIR) > /dev/null 2>&1 # Kick off the tests - adb shell "export LD_LIBRARY_PATH=/system/$(MBGL_ANDROID_LIBDIR):$(MBGL_ANDROID_LOCAL_WORK_DIR) && cd $(MBGL_ANDROID_LOCAL_WORK_DIR) && $(MBGL_ANDROID_DALVIKVM) -cp $(MBGL_ANDROID_LOCAL_WORK_DIR)/test.jar Main --gtest_filter=$$*" + adb shell "export LD_LIBRARY_PATH=$(MBGL_ANDROID_LOCAL_WORK_DIR) && cd $(MBGL_ANDROID_LOCAL_WORK_DIR) && chmod +x mbgl-test && ./mbgl-test --class_path=$(MBGL_ANDROID_LOCAL_WORK_DIR)/classes.dex --gtest_filter=$$*" # Gather the results and unpack them adb shell "cd $(MBGL_ANDROID_LOCAL_WORK_DIR) && tar -cvzf results.tgz test/fixtures/* > /dev/null 2>&1" diff --git a/platform/android/config.cmake b/platform/android/config.cmake index b553b5b8abd..440a679f5d0 100644 --- a/platform/android/config.cmake +++ b/platform/android/config.cmake @@ -85,23 +85,25 @@ target_link_libraries(mapbox-gl PRIVATE mbgl-filesource ) -## Test library ## - -set(MBGL_TEST_TARGET_TYPE "library") +## Test executable ## macro(mbgl_platform_test) target_sources(mbgl-test - PRIVATE platform/default/src/mbgl/test/main.cpp - - # Main test entry point - platform/android/src/test/main.jni.cpp + PRIVATE platform/android/src/test/test_runner.cpp + PRIVATE platform/android/src/test/runtime.cpp ) target_include_directories(mbgl-test PRIVATE platform/android ) + set_target_properties(mbgl-test + PROPERTIES + LINK_FLAGS + "-fPIE -pie \ + -Wl,--export-dynamic \ + -Wl,--version-script=${CMAKE_SOURCE_DIR}/platform/android/src/test/version-script") + target_link_libraries(mbgl-test - PRIVATE mbgl-core PRIVATE mbgl-filesource ) endmacro() diff --git a/platform/android/src/test/Main.java b/platform/android/src/test/Main.java deleted file mode 100644 index b0f3aeb7b9f..00000000000 --- a/platform/android/src/test/Main.java +++ /dev/null @@ -1,15 +0,0 @@ - -public class Main { - public native void runAllTests(String[] args); - - public static void main(String[] args) throws Exception { - // Load the tests - System.loadLibrary("mbgl-test"); - - // Run the tests - new Main().runAllTests(args); - - // Exit explicitly otherwise dalvikvm won't quit - System.exit(0); - } -} diff --git a/platform/android/src/test/main.jni.cpp b/platform/android/src/test/main.jni.cpp deleted file mode 100644 index 1cd0d26d2c5..00000000000 --- a/platform/android/src/test/main.jni.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include "../jni.hpp" - -#include - -#include -#include - -#include - -namespace { - -// Main class (entry point for tests from dalvikvm) -struct Main { - static constexpr auto Name() { - return "Main"; - } - - /** - * JNI Bound to Main#runAllTests() - */ - static void runAllTests(jni::JNIEnv& env, const jni::Object
&, const jni::Array& args) { - mbgl::Log::Warning(mbgl::Event::JNI, "Starting tests"); - - // We need to create a copy of the argv data since Java-internals are stored in UTF-16. - std::vector data; - // Add a fake first argument to align indices. Google Test expects the first argument to - // start at index 1; index 0 is the name of the executable. - data.push_back("main.jar"); - const int argc = args.Length(env); - for (auto i = 0; i < argc; i++) { - data.emplace_back(jni::Make(env, args.Get(env, i))); - } - - // Create an array of char pointers that point back to the data array. - std::vector argv; - for (const auto& arg : data) { - argv.push_back(arg.data()); - } - mbgl::runTests(argv.size(), const_cast(argv.data())); - } -}; - -} // namespace - -// We're declaring the function, which is libandroid_runtime.so. -// It is defined in AndroidRuntime.cpp: -// https://github.com/android/platform_frameworks_base/blob/master/core/jni/AndroidRuntime.cpp -// Once this symbol goes away, we'll have to revisit. -// This method loads and registers all of the Android-native frameworks so that we can use -// android.util.Log, android.graphics.Bitmap and so on. -// Setting the weak attribute tells the linker that this symbol is loaded at runtime and avoids -// a missing symbol error. -namespace android { -struct AndroidRuntime { - static int startReg(JNIEnv* env) __attribute__((weak)); -}; -} // namespace android - -// Main entry point -extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *) { - // Load Android-native jni bindings - jni::JNIEnv& env = jni::GetEnv(*vm, jni::jni_version_1_6); - android::AndroidRuntime::startReg(&env); - - // Load the main library jni bindings - mbgl::Log::Info(mbgl::Event::JNI, "Registering main JNI Methods"); - mbgl::android::registerNatives(vm); - - // Load the test library jni bindings - mbgl::Log::Info(mbgl::Event::JNI, "Registering test JNI Methods"); - - jni::RegisterNatives(env, *jni::Class
::Find(env), - jni::MakeNativeMethod("runAllTests")); - - return JNI_VERSION_1_6; -} diff --git a/platform/android/src/test/runtime.cpp b/platform/android/src/test/runtime.cpp new file mode 100644 index 00000000000..670c35e0774 --- /dev/null +++ b/platform/android/src/test/runtime.cpp @@ -0,0 +1,73 @@ +#include "runtime.hpp" +#include "../jni.hpp" + +#include +#include +#include +#include +#include + +// Required for art / libsigchain +extern "C" JNIEXPORT void EnsureFrontOfChain(int, struct sigaction*) { } +extern "C" JNIEXPORT void AddSpecialSignalHandlerFn(int, void*) { } +extern "C" JNIEXPORT void RemoveSpecialSignalHandlerFn(int, bool (*) (int, siginfo_t*, void*)) { } + +namespace { +const std::string kClassPathCommand{"--class_path="}; +const std::string kClassPath{"-Djava.class.path="}; +const std::string kDefaultDex{"/data/local/tmp/core-tests/classes.dex"}; +} // namespace + +namespace mbgl { +namespace android { + +bool init_runtime(int argc, char *argv[]) { + void* vmHandle = dlopen ("libart.so", RTLD_NOW); + assert(vmHandle != nullptr); + + using CreateJavaVMFn = jint (*) (JavaVM ** vm, JNIEnv ** env, void * vmArgs); + CreateJavaVMFn createJavaVMFn = reinterpret_cast(dlsym(vmHandle, "JNI_CreateJavaVM")); + assert(createJavaVMFn != nullptr); + + std::string classPath = kClassPath + kDefaultDex; + for (int i = 0; i < argc; ++i) { + const std::string arg{argv[i]}; + if (arg.compare(0, kClassPathCommand.length(), kClassPathCommand) == 0) { + classPath = kClassPath + arg.substr(kClassPathCommand.length()); + break; + } + } + + JavaVMOption options[1]; + options[0].optionString = classPath.c_str(); + + JavaVMInitArgs args; + args.version = JNI_VERSION_1_6; + args.nOptions = 1; + args.options = options; + args.ignoreUnrecognized = JNI_TRUE; + + JavaVM * vm; + JNIEnv * env; + + if (createJavaVMFn(&vm, &env, &args) != JNI_OK) { + return false; + } + + void* runtimeHandle = dlopen ("libandroid_runtime.so", RTLD_NOW); + assert(runtimeHandle != nullptr); + + using RegisterNativesFn = jint (*) (JNIEnv * env); + RegisterNativesFn registerNativesFn = reinterpret_cast(dlsym(runtimeHandle, "registerFrameworkNatives")); + assert(registerNativesFn != nullptr); + + if (registerNativesFn(env) != JNI_OK) { + return false; + } + + mbgl::android::registerNatives(vm); + return true; +} + +} // namespace android +} // namespace mbgl diff --git a/platform/android/src/test/runtime.hpp b/platform/android/src/test/runtime.hpp new file mode 100644 index 00000000000..6408a33cb71 --- /dev/null +++ b/platform/android/src/test/runtime.hpp @@ -0,0 +1,9 @@ +#pragma once + +namespace mbgl { +namespace android { + +bool init_runtime(int argc, char *argv[]); + +} // namespace android +} // namespace mbgl diff --git a/platform/android/src/test/test_runner.cpp b/platform/android/src/test/test_runner.cpp new file mode 100644 index 00000000000..aebaa1719d6 --- /dev/null +++ b/platform/android/src/test/test_runner.cpp @@ -0,0 +1,10 @@ +#include "runtime.hpp" +#include + +int main(int argc, char *argv[]) { + if (!mbgl::android::init_runtime(argc, argv)) { + return 1; + } + + mbgl::runTests(argc, argv); +} diff --git a/platform/android/src/test/version-script b/platform/android/src/test/version-script new file mode 100644 index 00000000000..ebc2c170181 --- /dev/null +++ b/platform/android/src/test/version-script @@ -0,0 +1,8 @@ +{ +global: + EnsureFrontOfChain; + AddSpecialSignalHandlerFn; + RemoveSpecialSignalHandlerFn; +local: + *; +};