1 /*
2 * Copyright (c) 2024 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 #include <fcntl.h>
17 #include <filesystem>
18 #include <functional>
19 #include <future>
20 #include <securec.h>
21 #include <sys/mman.h>
22 #include "gtest/gtest.h"
23 #include "common_utilities_hpp.h"
24 #include "extension_c_api.h"
25 #include "extension_executor.h"
26 #include "nlohmann/json.hpp"
27
28 using namespace OHOS::uitest;
29 using namespace std;
30
31 // dummy uirecord implmentations, to get rid of depending on 'uitest_uirecord'
32 namespace OHOS::uitest {
33 static auto g_dummyRecordEvent = R"({})";
UiDriverRecordStart(function<void (nlohmann::json)> handler,string modeOpt)34 int32_t UiDriverRecordStart(function<void(nlohmann::json)> handler, string modeOpt)
35 {
36 if (handler != nullptr) {
37 handler(nlohmann::json::parse(g_dummyRecordEvent));
38 }
39 return 0;
40 }
UiDriverRecordStop()41 void UiDriverRecordStop() {}
42
43 // test fixture
44 class ExtensionTest : public testing::Test {
45 public:
46 ~ExtensionTest() override = default;
47 protected:
SetUpTestSuite()48 static void SetUpTestSuite()
49 {
50 // redirect hook functions to my implmentations
51 auto addr0 = reinterpret_cast<uintptr_t>(ExtensionTest::ExtensionOnInitImpl);
52 auto addr1 = reinterpret_cast<uintptr_t>(ExtensionTest::ExtensionOnRunImpl);
53 setenv("OnInitImplAddr", to_string(addr0).c_str(), 1);
54 setenv("OnRunImplAddr", to_string(addr1).c_str(), 1);
55 }
56
SetUp()57 void SetUp() override
58 {
59 portCapture.getAndClearLastError = nullptr;
60 portCapture.printLog = nullptr;
61 portCapture.getUiTestVersion = nullptr;
62 portCapture.initLowLevelFunctions = nullptr;
63 argcCapture = 0;
64 argvCapture = nullptr;
65 onInitRet = RETCODE_SUCCESS;
66 onRunRet = RETCODE_SUCCESS;
67 }
68
69 // overwrite these two values to set the return value of extension hook-functions
70 static RetCode onInitRet;
71 static RetCode onRunRet;
72 static UiTestPort portCapture;
73 static size_t argcCapture;
74 static const char **argvCapture;
75 private:
ExtensionOnInitImpl(UiTestPort port,size_t argc,const char ** argv)76 static RetCode ExtensionOnInitImpl(UiTestPort port, size_t argc, const char **argv)
77 {
78 portCapture = port;
79 argcCapture = argc;
80 argvCapture = argv;
81 return onInitRet;
82 }
83
ExtensionOnRunImpl()84 static RetCode ExtensionOnRunImpl()
85 {
86 return onRunRet;
87 }
88 };
89
90 RetCode ExtensionTest::onInitRet = RETCODE_SUCCESS;
91 RetCode ExtensionTest::onRunRet = RETCODE_SUCCESS;
92 UiTestPort ExtensionTest::portCapture;
93 size_t ExtensionTest::argcCapture;
94 const char **ExtensionTest::argvCapture;
95
CheckExtensionLibExist()96 static bool CheckExtensionLibExist()
97 {
98 if (!filesystem::exists("/data/local/tmp/agent.so")) {
99 fprintf(stdout, "EXTENSION_LIBRARY_NOT_PRESENT!\n");
100 fprintf(stderr, "EXTENSION_LIBRARY_NOT_PRESENT!\n");
101 return false;
102 }
103 return true;
104 }
105
TEST_F(ExtensionTest,testHookFuncs)106 TEST_F(ExtensionTest, testHookFuncs)
107 {
108 if (!CheckExtensionLibExist()) {
109 return;
110 }
111 onInitRet = RETCODE_SUCCESS;
112 onRunRet = RETCODE_SUCCESS;
113 // should run both hooks and return true
114 ASSERT_TRUE(ExecuteExtension("0", 0, nullptr));
115 onInitRet = RETCODE_SUCCESS;
116 onRunRet = RETCODE_FAIL;
117 // should run both hooks and return false since onRunRet=FAIL
118 ASSERT_FALSE(ExecuteExtension("0", 0, nullptr));
119 onInitRet = RETCODE_FAIL;
120 onRunRet = RETCODE_SUCCESS;
121 // should run both hooks and return false since onInitRet=FAIL
122 ASSERT_FALSE(ExecuteExtension("0", 0, nullptr));
123 }
124
TEST_F(ExtensionTest,testPassArgs)125 TEST_F(ExtensionTest, testPassArgs)
126 {
127 if (!CheckExtensionLibExist()) {
128 return;
129 }
130 char arg0[1];
131 char arg1[1];
132 char *argv[] = {arg0, arg1};
133 const int32_t argc = sizeof(argv) / sizeof(argv[0]);
134 ExecuteExtension("0", argc, argv);
135 ASSERT_EQ(argc, argcCapture);
136 ASSERT_EQ(reinterpret_cast<uintptr_t>(argv), reinterpret_cast<uintptr_t>(argvCapture));
137 }
138
TEST_F(ExtensionTest,testPassArgsExtName)139 TEST_F(ExtensionTest, testPassArgsExtName)
140 {
141 if (!CheckExtensionLibExist()) {
142 return;
143 }
144 static constexpr uint32_t ARG_BUF_SIZE = 32;
145 char arg0[ARG_BUF_SIZE];
146 char arg1[ARG_BUF_SIZE];
147 char arg2[ARG_BUF_SIZE];
148 char arg3[ARG_BUF_SIZE];
149 memset_s(arg0, ARG_BUF_SIZE, 0, ARG_BUF_SIZE);
150 memset_s(arg1, ARG_BUF_SIZE, 0, ARG_BUF_SIZE);
151 memset_s(arg2, ARG_BUF_SIZE, 0, ARG_BUF_SIZE);
152 memset_s(arg3, ARG_BUF_SIZE, 0, ARG_BUF_SIZE);
153 memcpy_s(arg0, ARG_BUF_SIZE - 1, "--extension-name", strlen("--extension-name"));
154 memcpy_s(arg1, ARG_BUF_SIZE - 1, "agent.so", strlen("agent.so"));
155 memcpy_s(arg2, ARG_BUF_SIZE - 1, "--arg1", strlen("--arg1"));
156 memcpy_s(arg3, ARG_BUF_SIZE - 1, "--val1", strlen("--val1"));
157 char *argv[] = {arg0, arg1, arg2, arg3};
158 const int32_t argc = sizeof(argv) / sizeof(argv[0]);
159 ExecuteExtension("0", argc, argv);
160 // argv0,1 gives the extension name, should not be pass down
161 ASSERT_EQ(argc - TWO, argcCapture);
162 ASSERT_STREQ(argv[TWO], argvCapture[0]);
163 ASSERT_STREQ(argv[THREE], argvCapture[1]);
164 // illegal extension-name, should cause run failure
165 memcpy_s(arg1, ARG_BUF_SIZE - 1, "notexist.so", strlen("notexist.so"));
166 ASSERT_FALSE(ExecuteExtension("0", argc, argv));
167 }
168
TEST_F(ExtensionTest,testExtensionPorts)169 TEST_F(ExtensionTest, testExtensionPorts)
170 {
171 if (!CheckExtensionLibExist()) {
172 return;
173 }
174 ExecuteExtension("0", 0, nullptr);
175 ASSERT_NE(nullptr, portCapture.getUiTestVersion);
176 ASSERT_NE(nullptr, portCapture.printLog);
177 ASSERT_NE(nullptr, portCapture.getAndClearLastError);
178 ASSERT_NE(nullptr, portCapture.initLowLevelFunctions);
179 // load and check low-level-functions
180 // if recv arg is null, should fail
181 ASSERT_EQ(RETCODE_FAIL, portCapture.initLowLevelFunctions(nullptr));
182 LowLevelFunctions llfs;
183 ASSERT_EQ(RETCODE_SUCCESS, portCapture.initLowLevelFunctions(&llfs));
184 ASSERT_NE(nullptr, llfs.callThroughMessage);
185 ASSERT_NE(nullptr, llfs.setCallbackMessageHandler);
186 ASSERT_NE(nullptr, llfs.startCapture);
187 ASSERT_NE(nullptr, llfs.stopCapture);
188 ASSERT_NE(nullptr, llfs.atomicTouch);
189 }
190
TEST_F(ExtensionTest,testGetUiTestVersion)191 TEST_F(ExtensionTest, testGetUiTestVersion)
192 {
193 if (!CheckExtensionLibExist()) {
194 return;
195 }
196 static constexpr string_view VERSION = "1.2.3.4";
197 ASSERT_TRUE(ExecuteExtension(VERSION, 0, nullptr));
198 static constexpr size_t bufSize = 16;
199 uint8_t buf[bufSize] = { 0 };
200 size_t outSize = 0;
201 auto recv = ReceiveBuffer {.data = buf, .capacity = bufSize, .size = &outSize};
202 ASSERT_EQ(RETCODE_SUCCESS, portCapture.getUiTestVersion(recv));
203 ASSERT_EQ(VERSION.length(), outSize);
204 ASSERT_EQ(0, memcmp(VERSION.data(), buf, VERSION.length()));
205 // receive with not-enough buf, should cause failure
206 auto recvShort = ReceiveBuffer {.data = buf, .capacity = VERSION.length() - 1, .size = &outSize};
207 ASSERT_EQ(RETCODE_FAIL, portCapture.getUiTestVersion(recvShort));
208 }
209
TEST_F(ExtensionTest,testGetAndClearLastError)210 TEST_F(ExtensionTest, testGetAndClearLastError)
211 {
212 if (!CheckExtensionLibExist()) {
213 return;
214 }
215 ASSERT_TRUE(ExecuteExtension("0", 0, nullptr));
216 static constexpr size_t bufSize = 64;
217 uint8_t buf[bufSize] = { 0 };
218 size_t outSize = 0;
219 auto recv = ReceiveBuffer {.data = buf, .capacity = bufSize, .size = &outSize};
220 int32_t errCode = 0;
221 // code receiver is nullptr, cannot get
222 ASSERT_EQ(RETCODE_FAIL, portCapture.getAndClearLastError(nullptr, recv));
223 // trigger an error (pass an illegal receiver), get and check it
224 ASSERT_EQ(RETCODE_FAIL, portCapture.getUiTestVersion(ReceiveBuffer {.data = nullptr}));
225 ASSERT_EQ(RETCODE_SUCCESS, portCapture.getAndClearLastError(&errCode, recv));
226 ASSERT_NE(RETCODE_SUCCESS, errCode);
227 constexpr string_view expMsg = "Illegal buffer pointer";
228 ASSERT_EQ(0, memcmp(expMsg.data(), buf, expMsg.length()));
229 // error should be cleared after get
230 ASSERT_EQ(RETCODE_SUCCESS, portCapture.getAndClearLastError(&errCode, recv));
231 ASSERT_EQ(RETCODE_SUCCESS, errCode);
232 ASSERT_EQ(0, outSize);
233 }
234
PrintLog(UiTestPort & port,int32_t level,const char * format,...)235 static RetCode PrintLog(UiTestPort &port, int32_t level, const char *format, ...)
236 {
237 auto tag = Text { .data = "TAG", .size = strlen("TAG") };
238 auto fmt = Text { .data = format, .size = format == nullptr ? 0: strlen(format) };
239 va_list ap;
240 va_start(ap, format);
241 auto ret = port.printLog(level, tag, fmt, ap);
242 va_end(ap);
243 return ret;
244 }
245
TEST_F(ExtensionTest,testPrintLog)246 TEST_F(ExtensionTest, testPrintLog)
247 {
248 if (!CheckExtensionLibExist()) {
249 return;
250 }
251 static constexpr int32_t levelDebug = 3;
252 ASSERT_TRUE(ExecuteExtension("0", 0, nullptr));
253 // print normal log
254 ASSERT_EQ(RETCODE_SUCCESS, PrintLog(portCapture, levelDebug, "Name is %s, age is %d", "wyz", 1));
255 // print with illegal level should cause failure
256 ASSERT_EQ(RETCODE_FAIL, PrintLog(portCapture, levelDebug - 1, "Name is %s, age is %d", "wyz", 1));
257 // print with illegal format should cause failure
258 ASSERT_EQ(RETCODE_FAIL, PrintLog(portCapture, levelDebug - 1, "Name is %s, age is %d", "wyz", "1"));
259 // print with null format should cause failure
260 ASSERT_EQ(RETCODE_FAIL, PrintLog(portCapture, levelDebug, nullptr));
261 // print with too-long string should cause failure
262 auto chars = ".....................................................................";
263 auto ret = PrintLog(portCapture, levelDebug, "%s%s%s%s%s%s%s%s", chars, chars, chars,
264 chars, chars, chars, chars, chars, chars, chars);
265 ASSERT_EQ(RETCODE_FAIL, ret);
266 }
267
268 struct CallThroughMessageTestParams {
269 const char *inMsg;
270 ReceiveBuffer recv;
271 bool *isFatalRecv;
272 const char *expOutMsg;
273 bool expSuccess;
274 };
275
276 static constexpr auto CALL_NORMAL = R"({"api":"BackendObjectsCleaner","this":null,"args":[]})";
277 static constexpr auto CALL_ILLJSON = R"({xxxx})";
278 static constexpr auto CALL_NO_API = R"({"this":null,"args":[]})";
279 static constexpr auto CALL_NO_THIS = R"({"api":"BackendObjectsCleaner","args":[]})";
280 static constexpr auto CALL_NO_ARGS = R"({"api":"BackendObjectsCleaner","this":null})";
281 static constexpr auto CALL_ILL_API = R"({"api":0,"this":null,"args":[]})";
282 static constexpr auto CALL_ILL_THIS = R"({"api":"BackendObjectsCleaner","this":false,"args":[]})";
283 static constexpr auto CALL_ILL_ARGS = R"({"api":"BackendObjectsCleaner","this":null,"args":""})";
284 static constexpr size_t BUF_SIZE = 128;
285 static uint8_t g_rBuf[BUF_SIZE] = { 0 };
286 static size_t g_rSize = 0;
287 static bool g_rFatal = false;
288
289 static constexpr CallThroughMessageTestParams PARAM_SUITE[] = {
290 {CALL_NORMAL, ReceiveBuffer {g_rBuf, BUF_SIZE, &g_rSize}, &g_rFatal, R"({"result":null})", true},
291 {nullptr, ReceiveBuffer {g_rBuf, BUF_SIZE, &g_rSize}, &g_rFatal, "Null message", false},
292 {CALL_NORMAL, ReceiveBuffer {nullptr, 0, &g_rSize}, &g_rFatal, "Null output buffer", false},
293 {CALL_NORMAL, ReceiveBuffer {g_rBuf, BUF_SIZE, nullptr}, &g_rFatal, "Null output size pointer", false},
294 {CALL_NORMAL, ReceiveBuffer {g_rBuf, BUF_SIZE, &g_rSize}, nullptr, "Null fatalError output pointer", false},
295 {CALL_ILLJSON, ReceiveBuffer {g_rBuf, BUF_SIZE, &g_rSize}, &g_rFatal, "parse json failed", false},
296 {CALL_NO_API, ReceiveBuffer {g_rBuf, BUF_SIZE, &g_rSize}, &g_rFatal, "api/this/args property missing", false},
297 {CALL_NO_THIS, ReceiveBuffer {g_rBuf, BUF_SIZE, &g_rSize}, &g_rFatal, "api/this/args property missing", false},
298 {CALL_NO_ARGS, ReceiveBuffer {g_rBuf, BUF_SIZE, &g_rSize}, &g_rFatal, "api/this/args property missing", false},
299 {CALL_ILL_API, ReceiveBuffer {g_rBuf, BUF_SIZE, &g_rSize}, &g_rFatal, "Illegal api value type", false},
300 {CALL_ILL_THIS, ReceiveBuffer {g_rBuf, BUF_SIZE, &g_rSize}, &g_rFatal, "Illegal thisRef type", false},
301 {CALL_ILL_ARGS, ReceiveBuffer {g_rBuf, BUF_SIZE, &g_rSize}, &g_rFatal, "Illegal api args type", false},
302 };
303
304
TEST_F(ExtensionTest,testCallThroughMessage)305 TEST_F(ExtensionTest, testCallThroughMessage)
306 {
307 if (!CheckExtensionLibExist()) {
308 return;
309 }
310 ASSERT_TRUE(ExecuteExtension("0", 0, nullptr));
311 LowLevelFunctions llfs;
312 ASSERT_EQ(RETCODE_SUCCESS, portCapture.initLowLevelFunctions(&llfs));
313 for (size_t index = 0; index < sizeof(PARAM_SUITE) / sizeof(PARAM_SUITE[0]); index++) {
314 auto &suite = PARAM_SUITE[index];
315 auto callIn = Text { .data = suite.inMsg, .size = suite.inMsg == nullptr? 0 : strlen(suite.inMsg) };
316 auto ret = llfs.callThroughMessage(callIn, suite.recv, suite.isFatalRecv);
317 ASSERT_EQ(suite.expSuccess, ret == RETCODE_SUCCESS) << "Unexpected ret code at index " << index;
318 if (suite.recv.data != nullptr && suite.recv.size != nullptr) {
319 auto outMsg = reinterpret_cast<const char *>(suite.recv.data);
320 auto checkOutMsg = strstr(outMsg, suite.expOutMsg) != nullptr;
321 ASSERT_TRUE(checkOutMsg) << "Unexpected output message at index " << index << ": " << outMsg;
322 }
323 }
324 }
325
326 static string g_recordCapture;
Callback(Text bytes)327 static void Callback(Text bytes)
328 {
329 g_recordCapture = string(reinterpret_cast<const char *>(bytes.data), bytes.size);
330 }
331
TEST_F(ExtensionTest,testSetCallbackMessageHandler)332 TEST_F(ExtensionTest, testSetCallbackMessageHandler)
333 {
334 if (!CheckExtensionLibExist()) {
335 return;
336 }
337 ASSERT_TRUE(ExecuteExtension("0", 0, nullptr));
338 LowLevelFunctions llfs;
339 ASSERT_EQ(RETCODE_SUCCESS, portCapture.initLowLevelFunctions(&llfs));
340 ASSERT_NE(RETCODE_SUCCESS, llfs.setCallbackMessageHandler(nullptr));
341 ASSERT_EQ(RETCODE_SUCCESS, llfs.setCallbackMessageHandler(Callback));
342 }
343
TEST_F(ExtensionTest,testCaptures)344 TEST_F(ExtensionTest, testCaptures)
345 {
346 if (!CheckExtensionLibExist()) {
347 return;
348 }
349 ASSERT_TRUE(ExecuteExtension("0", 0, nullptr));
350 LowLevelFunctions llfs;
351 ASSERT_EQ(RETCODE_SUCCESS, portCapture.initLowLevelFunctions(&llfs));
352 auto goodName = Text{ .data = "recordUiAction", .size = strlen("recordUiAction") };
353 auto goodOpt = Text{ .data = "{}", .size = strlen("{}") };
354 auto illName = Text{ .data = "haha", .size = strlen("haha") };
355 auto illOpt = Text{ .data = "{xxxx}", .size = strlen("{xxxx}") };
356 uint8_t errBuf[BUF_SIZE];
357 size_t msgSizeRecv = 0;
358 int32_t codeRecv = 0;
359 ReceiveBuffer msgRecv = { .data = errBuf, .capacity = BUF_SIZE, .size = &msgSizeRecv };
360 // test illegal inputs
361 ASSERT_NE(RETCODE_SUCCESS, llfs.startCapture(illName, Callback, goodOpt));
362 portCapture.getAndClearLastError(&codeRecv, msgRecv);
363 ASSERT_EQ(0, memcmp(errBuf, "Illegal capture type: haha", msgSizeRecv));
364 ASSERT_NE(RETCODE_SUCCESS, llfs.startCapture(goodName, nullptr, goodOpt));
365 portCapture.getAndClearLastError(&codeRecv, msgRecv);
366 ASSERT_EQ(0, memcmp(errBuf, "Illegal name/callback", msgSizeRecv));
367 ASSERT_NE(RETCODE_SUCCESS, llfs.startCapture(goodName, Callback, illOpt));
368 portCapture.getAndClearLastError(&codeRecv, msgRecv);
369 ASSERT_EQ(0, memcmp(errBuf, "Illegal optJson format", msgSizeRecv));
370 ASSERT_NE(RETCODE_SUCCESS, llfs.stopCapture(illName));
371 portCapture.getAndClearLastError(&codeRecv, msgRecv);
372 ASSERT_EQ(0, memcmp(errBuf, "Illegal capture type: haha", msgSizeRecv));
373 // test normal inputs
374 auto firstStartRet = llfs.startCapture(goodName, Callback, goodOpt);
375 auto secondStartRet = llfs.startCapture(goodName, Callback, goodOpt);
376 ASSERT_EQ(RETCODE_SUCCESS, firstStartRet);
377 ASSERT_NE(RETCODE_SUCCESS, secondStartRet);
378 portCapture.getAndClearLastError(&codeRecv, msgRecv);
379 ASSERT_EQ(0, memcmp(errBuf, "Capture already running: recordUiAction", msgSizeRecv));
380 // stop always returns true event if not running
381 ASSERT_EQ(RETCODE_SUCCESS, llfs.stopCapture(goodName));
382 ASSERT_EQ(RETCODE_SUCCESS, llfs.stopCapture(goodName));
383 // check the capture data can be delivered to the callback
384 // the ui_record implementation is mocke @line:31, set a dummy event here
385 g_dummyRecordEvent = R"({"data":"dummy_record"})";
386 ASSERT_EQ(RETCODE_SUCCESS, llfs.startCapture(goodName, Callback, goodOpt));
387 // callback should receive the event
388 sleep(1); // let record fire
389 ASSERT_STREQ(g_dummyRecordEvent, g_recordCapture.c_str()) << "Capture not callbacked!";
390 ASSERT_EQ(RETCODE_SUCCESS, llfs.stopCapture(goodName));
391 }
392 }