1 /** 2 * Copyright (c) 2024-2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 #ifndef PANDA_PLUGINS_ETS_ANI_GTEST_H 17 #define PANDA_PLUGINS_ETS_ANI_GTEST_H 18 19 #include <gtest/gtest.h> 20 #include <cstdlib> 21 #include <optional> 22 #include <vector> 23 24 #include "libpandabase/macros.h" 25 #include "plugins/ets/runtime/ani/ani.h" 26 #include "plugins/ets/runtime/ani/scoped_objects_fix.h" 27 28 namespace ark::ets::ani::testing { 29 30 class AniTest : public ::testing::Test { 31 public: SetUp()32 void SetUp() override 33 { 34 const char *stdlib = std::getenv("ARK_ETS_STDLIB_PATH"); 35 ASSERT_NE(stdlib, nullptr); 36 37 // Create boot-panda-files options 38 std::string bootFileString = "--ext:boot-panda-files=" + std::string(stdlib); 39 const char *abcPath = std::getenv("ANI_GTEST_ABC_PATH"); 40 if (abcPath != nullptr) { 41 bootFileString += ":"; 42 bootFileString += abcPath; 43 } 44 45 ani_option bootFileOption = {bootFileString.data(), nullptr}; 46 47 std::vector<ani_option> options; 48 options.push_back(bootFileOption); 49 50 // Add extra test-specific ANI options 51 for (auto &aniOpt : GetExtraAniOptions()) { 52 options.push_back(aniOpt); 53 } 54 55 ani_options optionsPtr = {options.size(), options.data()}; 56 ASSERT_TRUE(ANI_CreateVM(&optionsPtr, ANI_VERSION_1, &vm_) == ANI_OK); 57 58 // Get ANI API 59 ASSERT_TRUE(vm_->GetEnv(ANI_VERSION_1, &env_) == ANI_OK) << "Cannot get ani env"; 60 uint32_t aniVersion; 61 ASSERT_TRUE(env_->GetVersion(&aniVersion) == ANI_OK) << "Cannot get ani version"; 62 ASSERT_TRUE(aniVersion == ANI_VERSION_1) << "Incorrect ani version"; 63 } 64 TearDown()65 void TearDown() override 66 { 67 ASSERT_TRUE(vm_->DestroyVM() == ANI_OK) << "Cannot destroy ANI VM"; 68 } 69 70 /// Call function with name `fnName` from module denoted by `moduleDescriptor` 71 template <typename R, typename... Args> CallEtsFunction(const std::string & moduleName,const std::string & fnName,Args &&...args)72 R CallEtsFunction(const std::string &moduleName, const std::string &fnName, Args &&...args) 73 { 74 std::optional<R> result; 75 auto moduleDescriptor = "L" + moduleName + ";"; 76 CallEtsFunctionImpl(&result, moduleDescriptor, fnName, std::forward<Args>(args)...); 77 if constexpr (!std::is_same_v<R, void>) { 78 return result.value(); 79 } 80 } 81 82 class NativeFunction { 83 public: 84 template <typename FuncT> NativeFunction(const char * moduleDescriptor,const char * functionName,FuncT nativeFunction)85 NativeFunction(const char *moduleDescriptor, const char *functionName, FuncT nativeFunction) 86 : moduleDescriptor_(moduleDescriptor), 87 functionName_(functionName), 88 nativeFunction_(reinterpret_cast<void *>(nativeFunction)) 89 { 90 } 91 GetModule()92 const char *GetModule() const 93 { 94 return moduleDescriptor_; 95 } 96 GetName()97 const char *GetName() const 98 { 99 return functionName_; 100 } 101 GetNativePtr()102 void *GetNativePtr() const 103 { 104 return nativeFunction_; 105 } 106 107 private: 108 const char *moduleDescriptor_ {nullptr}; 109 const char *functionName_ {nullptr}; 110 void *nativeFunction_ {nullptr}; 111 }; 112 113 template <typename R, typename... Args> CallEtsNativeMethod(const NativeFunction & fn,Args &&...args)114 R CallEtsNativeMethod(const NativeFunction &fn, Args &&...args) 115 { 116 std::optional<R> result; 117 118 CallEtsNativeMethodImpl(&result, fn, std::forward<Args>(args)...); 119 120 if constexpr (!std::is_same_v<R, void>) { 121 return result.value(); 122 } 123 } 124 GetStdString(ani_string str,std::string & result)125 void GetStdString(ani_string str, std::string &result) 126 { 127 GetStdString(env_, str, result); 128 } 129 GetStdString(ani_env * env,ani_string str,std::string & result)130 static void GetStdString(ani_env *env, ani_string str, std::string &result) 131 { 132 ani_size sz {}; 133 ASSERT_EQ(env->String_GetUTF8Size(str, &sz), ANI_OK); 134 135 result.resize(sz + 1); 136 ASSERT_EQ(env->String_GetUTF8SubString(str, 0, sz, result.data(), result.size(), &sz), ANI_OK); 137 result.resize(sz); 138 } 139 140 bool IsRuntimeClassInitialized(std::string_view classDescriptor, bool isClass = true) 141 { 142 PandaString desc; 143 if (isClass) { 144 desc = Mangle::ConvertDescriptor(classDescriptor, true); 145 } else { 146 desc = Mangle::ConvertDescriptor(std::string(classDescriptor) + ".ETSGLOBAL", true); 147 } 148 PandaEnv *pandaEnv = PandaEnv::FromAniEnv(env_); 149 ScopedManagedCodeFix s(pandaEnv); 150 151 EtsClassLinker *classLinker = pandaEnv->GetEtsVM()->GetClassLinker(); 152 EtsClass *klass = classLinker->GetClass(desc.c_str(), true, GetClassLinkerContext(s.GetCoroutine())); 153 ASSERT(klass); 154 155 return klass->IsInitialized(); 156 } 157 158 protected: GetExtraAniOptions()159 virtual std::vector<ani_option> GetExtraAniOptions() 160 { 161 return {}; 162 } 163 164 private: GetFindModuleFailureMsg(const std::string & moduleDescriptor)165 static std::string GetFindModuleFailureMsg(const std::string &moduleDescriptor) 166 { 167 std::stringstream ss; 168 ss << "Failed to find module `" << moduleDescriptor << "`."; 169 return ss.str(); 170 } 171 GetFindFunctionFailureMsg(const std::string & moduleDescriptor,const std::string & fnName)172 static std::string GetFindFunctionFailureMsg(const std::string &moduleDescriptor, const std::string &fnName) 173 { 174 std::stringstream ss; 175 ss << "Failed to find function `" << fnName << "` in `" << moduleDescriptor << "`."; 176 return ss.str(); 177 } 178 179 template <typename R, typename... Args> CallEtsFunctionImpl(std::optional<R> * result,const std::string & moduleDescriptor,const std::string & fnName,Args &&...args)180 void CallEtsFunctionImpl(std::optional<R> *result, const std::string &moduleDescriptor, const std::string &fnName, 181 Args &&...args) 182 { 183 ani_module mod {}; 184 ASSERT_EQ(env_->FindModule(moduleDescriptor.c_str(), &mod), ANI_OK) 185 << GetFindModuleFailureMsg(moduleDescriptor); 186 ani_function fn {}; 187 ASSERT_EQ(env_->Module_FindFunction(mod, fnName.c_str(), nullptr, &fn), ANI_OK) 188 << GetFindFunctionFailureMsg(moduleDescriptor, fnName); 189 DoCallFunction(result, fn, std::forward<Args>(args)...); 190 } 191 192 template <typename R, typename... Args> CallEtsNativeMethodImpl(std::optional<R> * result,const NativeFunction & fn,Args &&...args)193 void CallEtsNativeMethodImpl(std::optional<R> *result, const NativeFunction &fn, Args &&...args) 194 { 195 const auto moduleDescriptor = std::string("L") + fn.GetModule() + ";"; 196 const auto *fnName = fn.GetName(); 197 ani_module mod {}; 198 ASSERT_EQ(env_->FindModule(moduleDescriptor.c_str(), &mod), ANI_OK) 199 << GetFindModuleFailureMsg(moduleDescriptor); 200 ani_function aniFn {}; 201 ASSERT_EQ(env_->Module_FindFunction(mod, fnName, nullptr, &aniFn), ANI_OK) 202 << GetFindFunctionFailureMsg(moduleDescriptor, fnName); 203 204 ani_native_function nativeFn = {fnName, nullptr, fn.GetNativePtr()}; 205 206 ASSERT_EQ(env_->Module_BindNativeFunctions(mod, &nativeFn, 1), ANI_OK) 207 << "Failed to register native function `" << fnName << "` in module " << moduleDescriptor << "."; 208 209 DoCallFunction(result, aniFn, std::forward<Args>(args)...); 210 } 211 212 // NOLINTBEGIN(cppcoreguidelines-pro-type-vararg) 213 template <typename R, typename... Args> DoCallFunction(std::optional<R> * result,ani_function fn,Args &&...args)214 void DoCallFunction(std::optional<R> *result, ani_function fn, Args &&...args) 215 { 216 std::conditional_t<std::is_same_v<R, void>, std::nullopt_t, R> value {}; 217 ani_status status; 218 219 if constexpr (std::is_same_v<R, ani_boolean>) { 220 status = env_->Function_Call_Boolean(fn, &value, std::forward<Args>(args)...); 221 } else if constexpr (std::is_same_v<R, ani_byte>) { 222 status = env_->Function_Call_Byte(fn, &value, std::forward<Args>(args)...); 223 } else if constexpr (std::is_same_v<R, ani_char>) { 224 status = env_->Function_Call_Char(fn, &value, std::forward<Args>(args)...); 225 } else if constexpr (std::is_same_v<R, ani_short>) { 226 status = env_->Function_Call_Short(fn, &value, std::forward<Args>(args)...); 227 } else if constexpr (std::is_same_v<R, ani_int>) { 228 status = env_->Function_Call_Int(fn, &value, std::forward<Args>(args)...); 229 } else if constexpr (std::is_same_v<R, ani_long>) { 230 status = env_->Function_Call_Long(fn, &value, std::forward<Args>(args)...); 231 } else if constexpr (std::is_same_v<R, ani_float>) { 232 status = env_->Function_Call_Float(fn, &value, std::forward<Args>(args)...); 233 } else if constexpr (std::is_same_v<R, ani_double>) { 234 status = env_->Function_Call_Double(fn, &value, std::forward<Args>(args)...); 235 } else if constexpr (std::is_same_v<R, void>) { 236 status = env_->Function_Call_Void(fn, std::forward<Args>(args)...); 237 value = std::nullopt; 238 } else if constexpr (std::is_same_v<R, ani_ref> || std::is_same_v<R, ani_tuple_value> || 239 std::is_same_v<R, ani_object>) { 240 ani_ref resultRef {}; 241 status = env_->Function_Call_Ref(fn, &resultRef, std::forward<Args>(args)...); 242 value = static_cast<R>(resultRef); 243 } else { 244 enum { INCORRECT_TEMPLATE_TYPE = false }; 245 static_assert(INCORRECT_TEMPLATE_TYPE, "Incorrect template type"); 246 } 247 248 if (status == ANI_PENDING_ERROR) { 249 ASSERT_EQ(env_->DescribeError(), ANI_OK); 250 } 251 ASSERT_EQ(status, ANI_OK); 252 253 result->emplace(value); 254 } 255 // NOLINTEND(cppcoreguidelines-pro-type-vararg) 256 GetClassLinkerContext(ark::ets::EtsCoroutine * coroutine)257 static ClassLinkerContext *GetClassLinkerContext(ark::ets::EtsCoroutine *coroutine) 258 { 259 auto stack = StackWalker::Create(coroutine); 260 if (!stack.HasFrame()) { 261 return nullptr; 262 } 263 264 auto *method = ark::ets::EtsMethod::FromRuntimeMethod(stack.GetMethod()); 265 if (method != nullptr) { 266 return method->GetClass()->GetLoadContext(); 267 } 268 return nullptr; 269 } 270 271 protected: 272 ani_env *env_ {nullptr}; // NOLINT(misc-non-private-member-variables-in-classes) 273 ani_vm *vm_ {nullptr}; // NOLINT(misc-non-private-member-variables-in-classes) 274 }; 275 276 } // namespace ark::ets::ani::testing 277 278 #endif // PANDA_PLUGINS_ETS_ANI_GTEST_H 279