• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }