• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2021-2023 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_INTEROP_JS_GTEST_H
17 #define PANDA_PLUGINS_ETS_INTEROP_JS_GTEST_H
18 
19 #include <fstream>
20 #include <sstream>
21 #include <gtest/gtest.h>
22 #include <node_api.h>
23 
24 #include "libpandabase/macros.h"
25 
26 namespace panda::ets::interop::js::testing {
27 
28 class EtsInteropTest : public ::testing::Test {
29 public:
SetUpTestSuite()30     static void SetUpTestSuite()
31     {
32         if (std::getenv("ARK_ETS_INTEROP_JS_GTEST_SOURCES") == nullptr) {
33             std::cerr << "ARK_ETS_INTEROP_JS_GTEST_SOURCES not set" << std::endl;
34             std::abort();
35         }
36     }
37 
SetUp()38     void SetUp() override
39     {
40         bool hasPendingExc;
41         [[maybe_unused]] napi_status status = napi_is_exception_pending(GetJsEnv(), &hasPendingExc);
42         ASSERT(status == napi_ok && !hasPendingExc);
43 
44         interopJsTestPath_ = std::getenv("ARK_ETS_INTEROP_JS_GTEST_SOURCES");
45         // This object is used to save global js names
46         if (!RunJsScript("var gtest_env = {};\n")) {
47             std::abort();
48         }
49     }
50 
RunJsScript(const std::string & script)51     [[nodiscard]] static bool RunJsScript(const std::string &script)
52     {
53         return DoRunJsScript(jsEnv_, script);
54     }
55 
56     template <typename R>
RunJsScriptByPath(const std::string & path)57     [[nodiscard]] std::optional<R> RunJsScriptByPath(const std::string &path)
58     {
59         return DoRunJsScriptByPath<R>(jsEnv_, path);
60     }
61 
RunJsTestSuite(const std::string & path)62     [[nodiscard]] bool RunJsTestSuite(const std::string &path)
63     {
64         return DoRunJsTestSuite(jsEnv_, path);
65     }
66 
67     template <typename R, typename... Args>
CallEtsMethod(std::string_view fnName,Args &&...args)68     [[nodiscard]] std::optional<R> CallEtsMethod(std::string_view fnName, Args &&...args)
69     {
70         return DoCallEtsMethod<R>(fnName, jsEnv_, std::forward<Args>(args)...);
71     }
72 
GetJsEnv()73     static napi_env GetJsEnv()
74     {
75         return jsEnv_;
76     }
77 
LoadModuleAs(const std::string & moduleName,const std::string & modulePath)78     void LoadModuleAs(const std::string &moduleName, const std::string &modulePath)
79     {
80         auto res =
81             RunJsScript("gtest_env." + moduleName + " = require(\"" + interopJsTestPath_ + "/" + modulePath + "\");\n");
82         if (!res) {
83             std::cerr << "Failed to load module: " << modulePath << std::endl;
84             std::abort();
85         }
86     }
87 
88 private:
DoRunJsTestSuite(napi_env env,const std::string & path)89     [[nodiscard]] bool DoRunJsTestSuite(napi_env env, const std::string &path)
90     {
91         std::string fullPath = interopJsTestPath_ + "/" + path;
92         std::string testSouceCode = ReadFile(fullPath);
93 
94         // NOTE:
95         //  We wrap the test source code to anonymous lambda function to avoid pollution the global namespace.
96         //  We also set the 'use strict' mode, because right now we are checking interop only in the strict mode.
97         return DoRunJsScript(env, "(() => { \n'use strict'\n" + testSouceCode + "\n})()");
98     }
99 
DoRunJsScript(napi_env env,const std::string & script)100     [[nodiscard]] static bool DoRunJsScript(napi_env env, const std::string &script)
101     {
102         [[maybe_unused]] napi_status status;
103         napi_value jsScript;
104         status = napi_create_string_utf8(env, script.c_str(), script.length(), &jsScript);
105         ASSERT(status == napi_ok);
106 
107         napi_value jsResult;
108         status = napi_run_script(env, jsScript, &jsResult);
109         if (status == napi_generic_failure) {
110             std::cerr << "EtsInteropTest fired an exception" << std::endl;
111             napi_value exc;
112             status = napi_get_and_clear_last_exception(env, &exc);
113             ASSERT(status == napi_ok);
114             napi_value jsStr;
115             status = napi_coerce_to_string(env, exc, &jsStr);
116             ASSERT(status == napi_ok);
117             std::cerr << "Exception string: " << GetString(env, jsStr) << std::endl;
118             return false;
119         }
120         return status == napi_ok;
121     }
122 
ReadFile(const std::string & fullPath)123     static std::string ReadFile(const std::string &fullPath)
124     {
125         std::ifstream jsSourceFile(fullPath);
126         if (!jsSourceFile.is_open()) {
127             std::cerr << __func__ << ": Cannot open file: " << fullPath << std::endl;
128             std::abort();
129         }
130         std::ostringstream stringStream;
131         stringStream << jsSourceFile.rdbuf();
132         return stringStream.str();
133     }
134 
135     template <typename T>
DoRunJsScriptByPath(napi_env env,const std::string & path)136     [[nodiscard]] std::optional<T> DoRunJsScriptByPath(napi_env env, const std::string &path)
137     {
138         std::string fullPath = interopJsTestPath_ + "/" + path;
139 
140         if (!DoRunJsScript(env, ReadFile(fullPath))) {
141             return std::nullopt;
142         }
143 
144         [[maybe_unused]] napi_status status;
145         napi_value jsRetValue {};
146         status = napi_get_named_property(env, GetJsGtestObject(env), "ret", &jsRetValue);
147         ASSERT(status == napi_ok);
148 
149         // Get globalThis.gtest.ret
150         return GetRetValue<T>(env, jsRetValue);
151     }
152 
GetString(napi_env env,napi_value jsStr)153     static std::string GetString(napi_env env, napi_value jsStr)
154     {
155         size_t length;
156         [[maybe_unused]] napi_status status = napi_get_value_string_utf8(env, jsStr, nullptr, 0, &length);
157         ASSERT(status == napi_ok);
158         std::string v(length, '\0');
159         size_t copied;
160         status = napi_get_value_string_utf8(env, jsStr, v.data(), length + 1, &copied);
161         ASSERT(status == napi_ok);
162         ASSERT(length == copied);
163         return v;
164     }
165 
166     template <typename T>
GetRetValue(napi_env env,napi_value jsValue)167     static T GetRetValue([[maybe_unused]] napi_env env, [[maybe_unused]] napi_value jsValue)
168     {
169         if constexpr (std::is_same_v<T, double>) {
170             double v;
171             [[maybe_unused]] napi_status status = napi_get_value_double(env, jsValue, &v);
172             ASSERT(status == napi_ok);
173             return v;
174         } else if constexpr (std::is_same_v<T, int32_t>) {
175             int32_t v;
176             [[maybe_unused]] napi_status status = napi_get_value_int32(env, jsValue, &v);
177             ASSERT(status == napi_ok);
178             return v;
179         } else if constexpr (std::is_same_v<T, uint32_t>) {
180             uint32_t v;
181             [[maybe_unused]] napi_status status = napi_get_value_uint32(env, jsValue, &v);
182             ASSERT(status == napi_ok);
183             return v;
184         } else if constexpr (std::is_same_v<T, int64_t>) {
185             int64_t v;
186             [[maybe_unused]] napi_status status = napi_get_value_int64(env, jsValue, &v);
187             ASSERT(status == napi_ok);
188             return v;
189         } else if constexpr (std::is_same_v<T, bool>) {
190             bool v;
191             [[maybe_unused]] napi_status status = napi_get_value_bool(env, jsValue, &v);
192             ASSERT(status == napi_ok);
193             return v;
194         } else if constexpr (std::is_same_v<T, std::string>) {
195             return GetString(env, jsValue);
196         } else if constexpr (std::is_same_v<T, napi_value>) {
197             return jsValue;
198         } else if constexpr (std::is_same_v<T, void>) {
199             // do nothing
200         } else {
201             enum { INCORRECT_TEMPLATE_TYPE = false };
202             static_assert(INCORRECT_TEMPLATE_TYPE, "Incorrect template type");
203         }
204     }
205 
MakeJsArg(napi_env env,double arg)206     static napi_value MakeJsArg(napi_env env, double arg)
207     {
208         napi_value v;
209         [[maybe_unused]] napi_status status = napi_create_double(env, arg, &v);
210         ASSERT(status == napi_ok);
211         return v;
212     }
213 
MakeJsArg(napi_env env,int32_t arg)214     static napi_value MakeJsArg(napi_env env, int32_t arg)
215     {
216         napi_value v;
217         [[maybe_unused]] napi_status status = napi_create_int32(env, arg, &v);
218         ASSERT(status == napi_ok);
219         return v;
220     }
221 
MakeJsArg(napi_env env,uint32_t arg)222     static napi_value MakeJsArg(napi_env env, uint32_t arg)
223     {
224         napi_value v;
225         [[maybe_unused]] napi_status status = napi_create_uint32(env, arg, &v);
226         ASSERT(status == napi_ok);
227         return v;
228     }
229 
MakeJsArg(napi_env env,int64_t arg)230     static napi_value MakeJsArg(napi_env env, int64_t arg)
231     {
232         napi_value v;
233         [[maybe_unused]] napi_status status = napi_create_int64(env, arg, &v);
234         ASSERT(status == napi_ok);
235         return v;
236     }
237 
MakeJsArg(napi_env env,std::string_view arg)238     static napi_value MakeJsArg(napi_env env, std::string_view arg)
239     {
240         napi_value v;
241         [[maybe_unused]] napi_status status = napi_create_string_utf8(env, arg.data(), arg.length(), &v);
242         ASSERT(status == napi_ok);
243         return v;
244     }
245 
MakeJsArg(napi_env env,napi_value arg)246     static napi_value MakeJsArg([[maybe_unused]] napi_env env, napi_value arg)
247     {
248         return arg;
249     }
250 
GetJsGtestObject(napi_env env)251     static napi_value GetJsGtestObject(napi_env env)
252     {
253         [[maybe_unused]] napi_status status;
254 
255         // Get globalThis
256         napi_value jsGlobalObject;
257         status = napi_get_global(env, &jsGlobalObject);
258         ASSERT(status == napi_ok);
259 
260         // Get globalThis.gtest
261         napi_value jsGtestObject;
262         status = napi_get_named_property(env, jsGlobalObject, "gtest", &jsGtestObject);
263         ASSERT(status == napi_ok);
264 
265         return jsGtestObject;
266     }
267 
268     template <typename R, typename... Args>
DoCallEtsMethod(std::string_view fnName,napi_env env,Args &&...args)269     [[nodiscard]] static std::optional<R> DoCallEtsMethod(std::string_view fnName, napi_env env, Args &&...args)
270     {
271         [[maybe_unused]] napi_status status;
272 
273         // Get globalThis.gtest
274         napi_value jsGtestObject = GetJsGtestObject(env);
275 
276         // Set globalThis.gtest.functionName
277         napi_value jsFunctionName;
278         status = napi_create_string_utf8(env, fnName.data(), fnName.length(), &jsFunctionName);
279         ASSERT(status == napi_ok);
280         status = napi_set_named_property(env, jsGtestObject, "functionName", jsFunctionName);
281         ASSERT(status == napi_ok);
282 
283         // Set globalThis.gtest.args
284         std::initializer_list<napi_value> napiArgs = {MakeJsArg(env, args)...};
285         napi_value jsArgs;
286         status = napi_create_array_with_length(env, napiArgs.size(), &jsArgs);
287         ASSERT(status == napi_ok);
288         uint32_t i = 0;
289         for (auto arg : napiArgs) {
290             status = napi_set_element(env, jsArgs, i++, arg);
291             ASSERT(status == napi_ok);
292         }
293         status = napi_set_named_property(env, jsGtestObject, "args", jsArgs);
294         ASSERT(status == napi_ok);
295 
296         // Call ETS method via JS
297         auto res = RunJsScript(R"(
298             gtest.ret = gtest.etsVm.call(gtest.functionName, ...gtest.args);
299         )");
300         if (!res) {
301             return std::nullopt;
302         }
303 
304         // Get globalThis.gtest.ret
305         napi_value jsRetValue {};
306         status = napi_get_named_property(env, jsGtestObject, "ret", &jsRetValue);
307         ASSERT(status == napi_ok);
308         return GetRetValue<R>(env, jsRetValue);
309     }
310 
311     friend class JsEnvAccessor;
312     static napi_env jsEnv_;
313 
314 protected:
315     // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
316     std::string interopJsTestPath_;
317 };
318 
319 }  // namespace panda::ets::interop::js::testing
320 
321 #endif  // !PANDA_PLUGINS_ETS_INTEROP_JS_GTEST_H
322