/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include "native_loader_namespace.h" #include "nativehelper/scoped_utf_chars.h" #include "nativeloader/dlext_namespaces.h" #include "nativeloader/native_loader.h" #include "public_libraries.h" namespace android { namespace nativeloader { using ::testing::Eq; using ::testing::Return; using ::testing::StrEq; using ::testing::_; using internal::ConfigEntry; using internal::ParseConfig; #if defined(__LP64__) #define LIB_DIR "lib64" #else #define LIB_DIR "lib" #endif // gmock interface that represents interested platform APIs on libdl and libnativebridge class Platform { public: virtual ~Platform() {} // libdl APIs virtual void* dlopen(const char* filename, int flags) = 0; virtual int dlclose(void* handle) = 0; virtual char* dlerror(void) = 0; // These mock_* are the APIs semantically the same across libdl and libnativebridge. // Instead of having two set of mock APIs for the two, define only one set with an additional // argument 'bool bridged' to identify the context (i.e., called for libdl or libnativebridge). typedef char* mock_namespace_handle; virtual bool mock_init_anonymous_namespace(bool bridged, const char* sonames, const char* search_paths) = 0; virtual mock_namespace_handle mock_create_namespace( bool bridged, const char* name, const char* ld_library_path, const char* default_library_path, uint64_t type, const char* permitted_when_isolated_path, mock_namespace_handle parent) = 0; virtual bool mock_link_namespaces(bool bridged, mock_namespace_handle from, mock_namespace_handle to, const char* sonames) = 0; virtual mock_namespace_handle mock_get_exported_namespace(bool bridged, const char* name) = 0; virtual void* mock_dlopen_ext(bool bridged, const char* filename, int flags, mock_namespace_handle ns) = 0; // libnativebridge APIs for which libdl has no corresponding APIs virtual bool NativeBridgeInitialized() = 0; virtual const char* NativeBridgeGetError() = 0; virtual bool NativeBridgeIsPathSupported(const char*) = 0; virtual bool NativeBridgeIsSupported(const char*) = 0; // To mock "ClassLoader Object.getParent()" virtual const char* JniObject_getParent(const char*) = 0; }; // The mock does not actually create a namespace object. But simply casts the pointer to the // string for the namespace name as the handle to the namespace object. #define TO_ANDROID_NAMESPACE(str) \ reinterpret_cast(const_cast(str)) #define TO_BRIDGED_NAMESPACE(str) \ reinterpret_cast(const_cast(str)) #define TO_MOCK_NAMESPACE(ns) reinterpret_cast(ns) // These represents built-in namespaces created by the linker according to ld.config.txt static std::unordered_map namespaces = { {"system", TO_MOCK_NAMESPACE(TO_ANDROID_NAMESPACE("system"))}, {"default", TO_MOCK_NAMESPACE(TO_ANDROID_NAMESPACE("default"))}, {"com_android_art", TO_MOCK_NAMESPACE(TO_ANDROID_NAMESPACE("com_android_art"))}, {"sphal", TO_MOCK_NAMESPACE(TO_ANDROID_NAMESPACE("sphal"))}, {"vndk", TO_MOCK_NAMESPACE(TO_ANDROID_NAMESPACE("vndk"))}, {"vndk_product", TO_MOCK_NAMESPACE(TO_ANDROID_NAMESPACE("vndk_product"))}, {"com_android_neuralnetworks", TO_MOCK_NAMESPACE(TO_ANDROID_NAMESPACE("com_android_neuralnetworks"))}, {"com_android_os_statsd", TO_MOCK_NAMESPACE(TO_ANDROID_NAMESPACE("com_android_os_statsd"))}, }; // The actual gmock object class MockPlatform : public Platform { public: explicit MockPlatform(bool is_bridged) : is_bridged_(is_bridged) { ON_CALL(*this, NativeBridgeIsSupported(_)).WillByDefault(Return(is_bridged_)); ON_CALL(*this, NativeBridgeIsPathSupported(_)).WillByDefault(Return(is_bridged_)); ON_CALL(*this, mock_get_exported_namespace(_, _)) .WillByDefault(testing::Invoke([](bool, const char* name) -> mock_namespace_handle { if (namespaces.find(name) != namespaces.end()) { return namespaces[name]; } return TO_MOCK_NAMESPACE(TO_ANDROID_NAMESPACE("(namespace not found")); })); } // Mocking libdl APIs MOCK_METHOD2(dlopen, void*(const char*, int)); MOCK_METHOD1(dlclose, int(void*)); MOCK_METHOD0(dlerror, char*()); // Mocking the common APIs MOCK_METHOD3(mock_init_anonymous_namespace, bool(bool, const char*, const char*)); MOCK_METHOD7(mock_create_namespace, mock_namespace_handle(bool, const char*, const char*, const char*, uint64_t, const char*, mock_namespace_handle)); MOCK_METHOD4(mock_link_namespaces, bool(bool, mock_namespace_handle, mock_namespace_handle, const char*)); MOCK_METHOD2(mock_get_exported_namespace, mock_namespace_handle(bool, const char*)); MOCK_METHOD4(mock_dlopen_ext, void*(bool, const char*, int, mock_namespace_handle)); // Mocking libnativebridge APIs MOCK_METHOD0(NativeBridgeInitialized, bool()); MOCK_METHOD0(NativeBridgeGetError, const char*()); MOCK_METHOD1(NativeBridgeIsPathSupported, bool(const char*)); MOCK_METHOD1(NativeBridgeIsSupported, bool(const char*)); // Mocking "ClassLoader Object.getParent()" MOCK_METHOD1(JniObject_getParent, const char*(const char*)); private: bool is_bridged_; }; static std::unique_ptr mock; // Provide C wrappers for the mock object. extern "C" { void* dlopen(const char* file, int flag) { return mock->dlopen(file, flag); } int dlclose(void* handle) { return mock->dlclose(handle); } char* dlerror(void) { return mock->dlerror(); } bool android_init_anonymous_namespace(const char* sonames, const char* search_path) { return mock->mock_init_anonymous_namespace(false, sonames, search_path); } struct android_namespace_t* android_create_namespace(const char* name, const char* ld_library_path, const char* default_library_path, uint64_t type, const char* permitted_when_isolated_path, struct android_namespace_t* parent) { return TO_ANDROID_NAMESPACE( mock->mock_create_namespace(false, name, ld_library_path, default_library_path, type, permitted_when_isolated_path, TO_MOCK_NAMESPACE(parent))); } bool android_link_namespaces(struct android_namespace_t* from, struct android_namespace_t* to, const char* sonames) { return mock->mock_link_namespaces(false, TO_MOCK_NAMESPACE(from), TO_MOCK_NAMESPACE(to), sonames); } struct android_namespace_t* android_get_exported_namespace(const char* name) { return TO_ANDROID_NAMESPACE(mock->mock_get_exported_namespace(false, name)); } void* android_dlopen_ext(const char* filename, int flags, const android_dlextinfo* info) { return mock->mock_dlopen_ext(false, filename, flags, TO_MOCK_NAMESPACE(info->library_namespace)); } // libnativebridge APIs bool NativeBridgeIsSupported(const char* libpath) { return mock->NativeBridgeIsSupported(libpath); } struct native_bridge_namespace_t* NativeBridgeGetExportedNamespace(const char* name) { return TO_BRIDGED_NAMESPACE(mock->mock_get_exported_namespace(true, name)); } struct native_bridge_namespace_t* NativeBridgeCreateNamespace( const char* name, const char* ld_library_path, const char* default_library_path, uint64_t type, const char* permitted_when_isolated_path, struct native_bridge_namespace_t* parent) { return TO_BRIDGED_NAMESPACE( mock->mock_create_namespace(true, name, ld_library_path, default_library_path, type, permitted_when_isolated_path, TO_MOCK_NAMESPACE(parent))); } bool NativeBridgeLinkNamespaces(struct native_bridge_namespace_t* from, struct native_bridge_namespace_t* to, const char* sonames) { return mock->mock_link_namespaces(true, TO_MOCK_NAMESPACE(from), TO_MOCK_NAMESPACE(to), sonames); } void* NativeBridgeLoadLibraryExt(const char* libpath, int flag, struct native_bridge_namespace_t* ns) { return mock->mock_dlopen_ext(true, libpath, flag, TO_MOCK_NAMESPACE(ns)); } bool NativeBridgeInitialized() { return mock->NativeBridgeInitialized(); } bool NativeBridgeInitAnonymousNamespace(const char* public_ns_sonames, const char* anon_ns_library_path) { return mock->mock_init_anonymous_namespace(true, public_ns_sonames, anon_ns_library_path); } const char* NativeBridgeGetError() { return mock->NativeBridgeGetError(); } bool NativeBridgeIsPathSupported(const char* path) { return mock->NativeBridgeIsPathSupported(path); } } // extern "C" // A very simple JNI mock. // jstring is a pointer to utf8 char array. We don't need utf16 char here. // jobject, jclass, and jmethodID are also a pointer to utf8 char array // Only a few JNI methods that are actually used in libnativeloader are mocked. JNINativeInterface* CreateJNINativeInterface() { JNINativeInterface* inf = new JNINativeInterface(); memset(inf, 0, sizeof(JNINativeInterface)); inf->GetStringUTFChars = [](JNIEnv*, jstring s, jboolean*) -> const char* { return reinterpret_cast(s); }; inf->ReleaseStringUTFChars = [](JNIEnv*, jstring, const char*) -> void { return; }; inf->NewStringUTF = [](JNIEnv*, const char* bytes) -> jstring { return reinterpret_cast(const_cast(bytes)); }; inf->FindClass = [](JNIEnv*, const char* name) -> jclass { return reinterpret_cast(const_cast(name)); }; inf->CallObjectMethodV = [](JNIEnv*, jobject obj, jmethodID mid, va_list) -> jobject { if (strcmp("getParent", reinterpret_cast(mid)) == 0) { // JniObject_getParent can be a valid jobject or nullptr if there is // no parent classloader. const char* ret = mock->JniObject_getParent(reinterpret_cast(obj)); return reinterpret_cast(const_cast(ret)); } return nullptr; }; inf->GetMethodID = [](JNIEnv*, jclass, const char* name, const char*) -> jmethodID { return reinterpret_cast(const_cast(name)); }; inf->NewWeakGlobalRef = [](JNIEnv*, jobject obj) -> jobject { return obj; }; inf->IsSameObject = [](JNIEnv*, jobject a, jobject b) -> jboolean { return strcmp(reinterpret_cast(a), reinterpret_cast(b)) == 0; }; return inf; } static void* const any_nonnull = reinterpret_cast(0x12345678); // Custom matcher for comparing namespace handles MATCHER_P(NsEq, other, "") { *result_listener << "comparing " << reinterpret_cast(arg) << " and " << other; return strcmp(reinterpret_cast(arg), reinterpret_cast(other)) == 0; } ///////////////////////////////////////////////////////////////// // Test fixture class NativeLoaderTest : public ::testing::TestWithParam { protected: bool IsBridged() { return GetParam(); } void SetUp() override { mock = std::make_unique>(IsBridged()); env = std::make_unique(); env->functions = CreateJNINativeInterface(); } void SetExpectations() { std::vector default_public_libs = android::base::Split(preloadable_public_libraries(), ":"); for (auto l : default_public_libs) { EXPECT_CALL(*mock, dlopen(StrEq(l.c_str()), RTLD_NOW | RTLD_NODELETE)) .WillOnce(Return(any_nonnull)); } } void RunTest() { InitializeNativeLoader(); } void TearDown() override { ResetNativeLoader(); delete env->functions; mock.reset(); } std::unique_ptr env; }; ///////////////////////////////////////////////////////////////// TEST_P(NativeLoaderTest, InitializeLoadsDefaultPublicLibraries) { SetExpectations(); RunTest(); } INSTANTIATE_TEST_SUITE_P(NativeLoaderTests, NativeLoaderTest, testing::Bool()); ///////////////////////////////////////////////////////////////// class NativeLoaderTest_Create : public NativeLoaderTest { protected: // Test inputs (initialized to the default values). Overriding these // must be done before calling SetExpectations() and RunTest(). uint32_t target_sdk_version = 29; std::string class_loader = "my_classloader"; bool is_shared = false; std::string dex_path = "/data/app/foo/classes.dex"; std::string library_path = "/data/app/foo/" LIB_DIR "/arm"; std::string permitted_path = "/data/app/foo/" LIB_DIR; // expected output (.. for the default test inputs) std::string expected_namespace_name = "classloader-namespace"; uint64_t expected_namespace_flags = ANDROID_NAMESPACE_TYPE_ISOLATED | ANDROID_NAMESPACE_TYPE_ALSO_USED_AS_ANONYMOUS; std::string expected_library_path = library_path; std::string expected_permitted_path = std::string("/data:/mnt/expand:") + permitted_path; std::string expected_parent_namespace = "system"; bool expected_link_with_platform_ns = true; bool expected_link_with_art_ns = true; bool expected_link_with_sphal_ns = !vendor_public_libraries().empty(); bool expected_link_with_vndk_ns = false; bool expected_link_with_vndk_product_ns = false; bool expected_link_with_default_ns = false; bool expected_link_with_neuralnetworks_ns = true; bool expected_link_with_statsd_ns = true; std::string expected_shared_libs_to_platform_ns = default_public_libraries(); std::string expected_shared_libs_to_art_ns = art_public_libraries(); std::string expected_shared_libs_to_sphal_ns = vendor_public_libraries(); std::string expected_shared_libs_to_vndk_ns = vndksp_libraries_vendor(); std::string expected_shared_libs_to_vndk_product_ns = vndksp_libraries_product(); std::string expected_shared_libs_to_default_ns = default_public_libraries(); std::string expected_shared_libs_to_neuralnetworks_ns = neuralnetworks_public_libraries(); std::string expected_shared_libs_to_statsd_ns = statsd_public_libraries(); void SetExpectations() { NativeLoaderTest::SetExpectations(); ON_CALL(*mock, JniObject_getParent(StrEq(class_loader))).WillByDefault(Return(nullptr)); EXPECT_CALL(*mock, NativeBridgeIsPathSupported(_)).Times(testing::AnyNumber()); EXPECT_CALL(*mock, NativeBridgeInitialized()).Times(testing::AnyNumber()); EXPECT_CALL(*mock, mock_create_namespace( Eq(IsBridged()), StrEq(expected_namespace_name), nullptr, StrEq(expected_library_path), expected_namespace_flags, StrEq(expected_permitted_path), NsEq(expected_parent_namespace.c_str()))) .WillOnce(Return(TO_MOCK_NAMESPACE(TO_ANDROID_NAMESPACE(dex_path.c_str())))); if (expected_link_with_platform_ns) { EXPECT_CALL(*mock, mock_link_namespaces(Eq(IsBridged()), _, NsEq("system"), StrEq(expected_shared_libs_to_platform_ns))) .WillOnce(Return(true)); } if (expected_link_with_art_ns) { EXPECT_CALL(*mock, mock_link_namespaces(Eq(IsBridged()), _, NsEq("com_android_art"), StrEq(expected_shared_libs_to_art_ns))) .WillOnce(Return(true)); } if (expected_link_with_sphal_ns) { EXPECT_CALL(*mock, mock_link_namespaces(Eq(IsBridged()), _, NsEq("sphal"), StrEq(expected_shared_libs_to_sphal_ns))) .WillOnce(Return(true)); } if (expected_link_with_vndk_ns) { EXPECT_CALL(*mock, mock_link_namespaces(Eq(IsBridged()), _, NsEq("vndk"), StrEq(expected_shared_libs_to_vndk_ns))) .WillOnce(Return(true)); } if (expected_link_with_vndk_product_ns) { EXPECT_CALL(*mock, mock_link_namespaces(Eq(IsBridged()), _, NsEq("vndk_product"), StrEq(expected_shared_libs_to_vndk_product_ns))) .WillOnce(Return(true)); } if (expected_link_with_default_ns) { EXPECT_CALL(*mock, mock_link_namespaces(Eq(IsBridged()), _, NsEq("default"), StrEq(expected_shared_libs_to_default_ns))) .WillOnce(Return(true)); } if (expected_link_with_neuralnetworks_ns) { EXPECT_CALL(*mock, mock_link_namespaces(Eq(IsBridged()), _, NsEq("com_android_neuralnetworks"), StrEq(expected_shared_libs_to_neuralnetworks_ns))) .WillOnce(Return(true)); } if (expected_link_with_statsd_ns) { EXPECT_CALL(*mock, mock_link_namespaces(Eq(IsBridged()), _, NsEq("com_android_os_statsd"), StrEq(expected_shared_libs_to_statsd_ns))) .WillOnce(Return(true)); } } void RunTest() { NativeLoaderTest::RunTest(); jstring err = CreateClassLoaderNamespace( env(), target_sdk_version, env()->NewStringUTF(class_loader.c_str()), is_shared, env()->NewStringUTF(dex_path.c_str()), env()->NewStringUTF(library_path.c_str()), env()->NewStringUTF(permitted_path.c_str())); // no error EXPECT_EQ(err, nullptr) << "Error is: " << std::string(ScopedUtfChars(env(), err).c_str()); if (!IsBridged()) { struct android_namespace_t* ns = FindNamespaceByClassLoader(env(), env()->NewStringUTF(class_loader.c_str())); // The created namespace is for this apk EXPECT_EQ(dex_path.c_str(), reinterpret_cast(ns)); } else { struct NativeLoaderNamespace* ns = FindNativeLoaderNamespaceByClassLoader(env(), env()->NewStringUTF(class_loader.c_str())); // The created namespace is for the this apk EXPECT_STREQ(dex_path.c_str(), reinterpret_cast(ns->ToRawNativeBridgeNamespace())); } } JNIEnv* env() { return NativeLoaderTest::env.get(); } }; TEST_P(NativeLoaderTest_Create, DownloadedApp) { SetExpectations(); RunTest(); } TEST_P(NativeLoaderTest_Create, BundledSystemApp) { dex_path = "/system/app/foo/foo.apk"; is_shared = true; expected_namespace_name = "classloader-namespace-shared"; expected_namespace_flags |= ANDROID_NAMESPACE_TYPE_SHARED; SetExpectations(); RunTest(); } TEST_P(NativeLoaderTest_Create, BundledVendorApp) { dex_path = "/vendor/app/foo/foo.apk"; is_shared = true; expected_namespace_name = "classloader-namespace-shared"; expected_namespace_flags |= ANDROID_NAMESPACE_TYPE_SHARED; SetExpectations(); RunTest(); } TEST_P(NativeLoaderTest_Create, UnbundledVendorApp) { dex_path = "/vendor/app/foo/foo.apk"; is_shared = false; expected_namespace_name = "vendor-classloader-namespace"; expected_library_path = expected_library_path + ":/vendor/" LIB_DIR; expected_permitted_path = expected_permitted_path + ":/vendor/" LIB_DIR; expected_shared_libs_to_platform_ns = expected_shared_libs_to_platform_ns + ":" + llndk_libraries_vendor(); expected_link_with_vndk_ns = true; SetExpectations(); RunTest(); } TEST_P(NativeLoaderTest_Create, BundledProductApp) { dex_path = "/product/app/foo/foo.apk"; is_shared = true; expected_namespace_name = "classloader-namespace-shared"; expected_namespace_flags |= ANDROID_NAMESPACE_TYPE_SHARED; SetExpectations(); RunTest(); } TEST_P(NativeLoaderTest_Create, UnbundledProductApp) { dex_path = "/product/app/foo/foo.apk"; is_shared = false; if (is_product_vndk_version_defined()) { expected_namespace_name = "vendor-classloader-namespace"; expected_library_path = expected_library_path + ":/product/" LIB_DIR ":/system/product/" LIB_DIR; expected_permitted_path = expected_permitted_path + ":/product/" LIB_DIR ":/system/product/" LIB_DIR; expected_shared_libs_to_platform_ns = expected_shared_libs_to_platform_ns + ":" + llndk_libraries_product(); expected_link_with_vndk_product_ns = true; } SetExpectations(); RunTest(); } TEST_P(NativeLoaderTest_Create, NamespaceForSharedLibIsNotUsedAsAnonymousNamespace) { if (IsBridged()) { // There is no shared lib in translated arch // TODO(jiyong): revisit this return; } // compared to apks, for java shared libs, library_path is empty; java shared // libs don't have their own native libs. They use platform's. library_path = ""; expected_library_path = library_path; // no ALSO_USED_AS_ANONYMOUS expected_namespace_flags = ANDROID_NAMESPACE_TYPE_ISOLATED; SetExpectations(); RunTest(); } TEST_P(NativeLoaderTest_Create, TwoApks) { SetExpectations(); const uint32_t second_app_target_sdk_version = 29; const std::string second_app_class_loader = "second_app_classloader"; const bool second_app_is_shared = false; const std::string second_app_dex_path = "/data/app/bar/classes.dex"; const std::string second_app_library_path = "/data/app/bar/" LIB_DIR "/arm"; const std::string second_app_permitted_path = "/data/app/bar/" LIB_DIR; const std::string expected_second_app_permitted_path = std::string("/data:/mnt/expand:") + second_app_permitted_path; const std::string expected_second_app_parent_namespace = "classloader-namespace"; // no ALSO_USED_AS_ANONYMOUS const uint64_t expected_second_namespace_flags = ANDROID_NAMESPACE_TYPE_ISOLATED; // The scenario is that second app is loaded by the first app. // So the first app's classloader (`classloader`) is parent of the second // app's classloader. ON_CALL(*mock, JniObject_getParent(StrEq(second_app_class_loader))) .WillByDefault(Return(class_loader.c_str())); // namespace for the second app is created. Its parent is set to the namespace // of the first app. EXPECT_CALL(*mock, mock_create_namespace( Eq(IsBridged()), StrEq(expected_namespace_name), nullptr, StrEq(second_app_library_path), expected_second_namespace_flags, StrEq(expected_second_app_permitted_path), NsEq(dex_path.c_str()))) .WillOnce(Return(TO_MOCK_NAMESPACE(TO_ANDROID_NAMESPACE(second_app_dex_path.c_str())))); EXPECT_CALL(*mock, mock_link_namespaces(Eq(IsBridged()), NsEq(second_app_dex_path.c_str()), _, _)) .WillRepeatedly(Return(true)); RunTest(); jstring err = CreateClassLoaderNamespace( env(), second_app_target_sdk_version, env()->NewStringUTF(second_app_class_loader.c_str()), second_app_is_shared, env()->NewStringUTF(second_app_dex_path.c_str()), env()->NewStringUTF(second_app_library_path.c_str()), env()->NewStringUTF(second_app_permitted_path.c_str())); // success EXPECT_EQ(err, nullptr) << "Error is: " << std::string(ScopedUtfChars(env(), err).c_str()); if (!IsBridged()) { struct android_namespace_t* ns = FindNamespaceByClassLoader(env(), env()->NewStringUTF(second_app_class_loader.c_str())); // The created namespace is for the second apk EXPECT_EQ(second_app_dex_path.c_str(), reinterpret_cast(ns)); } else { struct NativeLoaderNamespace* ns = FindNativeLoaderNamespaceByClassLoader( env(), env()->NewStringUTF(second_app_class_loader.c_str())); // The created namespace is for the second apk EXPECT_STREQ(second_app_dex_path.c_str(), reinterpret_cast(ns->ToRawNativeBridgeNamespace())); } } INSTANTIATE_TEST_SUITE_P(NativeLoaderTests_Create, NativeLoaderTest_Create, testing::Bool()); const std::function(const struct ConfigEntry&)> always_true = [](const struct ConfigEntry&) -> Result { return true; }; TEST(NativeLoaderConfigParser, NamesAndComments) { const char file_content[] = R"( ###### libA.so #libB.so libC.so libD.so #### libE.so )"; const std::vector expected_result = {"libA.so", "libC.so", "libD.so"}; Result> result = ParseConfig(file_content, always_true); ASSERT_RESULT_OK(result); ASSERT_EQ(expected_result, *result); } TEST(NativeLoaderConfigParser, WithBitness) { const char file_content[] = R"( libA.so 32 libB.so 64 libC.so )"; #if defined(__LP64__) const std::vector expected_result = {"libB.so", "libC.so"}; #else const std::vector expected_result = {"libA.so", "libC.so"}; #endif Result> result = ParseConfig(file_content, always_true); ASSERT_RESULT_OK(result); ASSERT_EQ(expected_result, *result); } TEST(NativeLoaderConfigParser, WithNoPreload) { const char file_content[] = R"( libA.so nopreload libB.so nopreload libC.so )"; const std::vector expected_result = {"libC.so"}; Result> result = ParseConfig(file_content, [](const struct ConfigEntry& entry) -> Result { return !entry.nopreload; }); ASSERT_RESULT_OK(result); ASSERT_EQ(expected_result, *result); } TEST(NativeLoaderConfigParser, WithNoPreloadAndBitness) { const char file_content[] = R"( libA.so nopreload 32 libB.so 64 nopreload libC.so 32 libD.so 64 libE.so nopreload )"; #if defined(__LP64__) const std::vector expected_result = {"libD.so"}; #else const std::vector expected_result = {"libC.so"}; #endif Result> result = ParseConfig(file_content, [](const struct ConfigEntry& entry) -> Result { return !entry.nopreload; }); ASSERT_RESULT_OK(result); ASSERT_EQ(expected_result, *result); } TEST(NativeLoaderConfigParser, RejectMalformed) { ASSERT_FALSE(ParseConfig("libA.so 32 64", always_true).ok()); ASSERT_FALSE(ParseConfig("libA.so 32 32", always_true).ok()); ASSERT_FALSE(ParseConfig("libA.so 32 nopreload 64", always_true).ok()); ASSERT_FALSE(ParseConfig("32 libA.so nopreload", always_true).ok()); ASSERT_FALSE(ParseConfig("nopreload libA.so 32", always_true).ok()); ASSERT_FALSE(ParseConfig("libA.so nopreload # comment", always_true).ok()); } } // namespace nativeloader } // namespace android