1/*
2 * Copyright (c) 2022 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 "assembler/assembly-emitter.h"
17#include "assembler/assembly-parser.h"
18
19#include "ecmascript/jspandafile/js_pandafile.h"
20#include "ecmascript/jspandafile/js_pandafile_manager.h"
21#include "ecmascript/tests/test_helper.h"
22#include "ecmascript/napi/include/jsnapi.h"
23#include "ecmascript/patch/quick_fix_manager.h"
24#include "ecmascript/jspandafile/program_object.h"
25
26using namespace panda::ecmascript;
27using namespace panda::panda_file;
28using namespace panda::pandasm;
29
30namespace panda::test {
31using PatchErrorCode = panda::JSNApi::PatchErrorCode;
32using Program = panda::ecmascript::Program;
33using EcmaContext = panda::ecmascript::EcmaContext;
34class QuickFixTest : public testing::Test {
35public:
36    static void SetUpTestCase()
37    {
38        GTEST_LOG_(INFO) << "SetUpTestCase";
39    }
40
41    static void TearDownTestCase()
42    {
43        GTEST_LOG_(INFO) << "TearDownCase";
44    }
45
46    void SetUp() override
47    {
48        TestHelper::CreateEcmaVMWithScope(instance, thread, scope, false, false, false);
49    }
50
51    void TearDown() override
52    {
53        TestHelper::DestroyEcmaVMWithScope(instance, scope, false);
54    }
55
56    EcmaVM *instance {nullptr};
57    EcmaHandleScope *scope {nullptr};
58    JSThread *thread {nullptr};
59};
60
61HWTEST_F_L0(QuickFixTest, HotReload_SingleFile)
62{
63    std::string baseFileName = QUICKFIX_ABC_PATH "single_file/base/index.abc";
64    std::string patchFileName = QUICKFIX_ABC_PATH "single_file/patch/index.abc";
65
66    JSNApi::EnableUserUncaughtErrorHandler(instance);
67
68    JSNApi::SetBundle(instance, false);
69
70    bool result = JSNApi::Execute(instance, baseFileName, "index");
71    EXPECT_TRUE(result);
72
73    auto res = JSNApi::LoadPatch(instance, patchFileName, baseFileName);
74    EXPECT_TRUE(res == PatchErrorCode::SUCCESS);
75
76    Local<ObjectRef> exception = JSNApi::GetAndClearUncaughtException(instance);
77    result = JSNApi::IsQuickFixCausedException(instance, exception, patchFileName);
78    EXPECT_FALSE(result);
79
80    res = JSNApi::UnloadPatch(instance, patchFileName);
81    EXPECT_TRUE(res == PatchErrorCode::SUCCESS);
82}
83
84HWTEST_F_L0(QuickFixTest, HotReload_Restart_SingleFile)
85{
86    std::string baseFileName = QUICKFIX_ABC_PATH "single_file/base/index.abc";
87    std::string patchFileName = QUICKFIX_ABC_PATH "single_file/patch/index.abc";
88
89    JSNApi::EnableUserUncaughtErrorHandler(instance);
90
91    JSNApi::SetBundle(instance, false);
92
93    bool result = JSNApi::Execute(instance, baseFileName, "index");
94    EXPECT_TRUE(result);
95
96    auto res = JSNApi::LoadPatch(instance, patchFileName, baseFileName);
97    EXPECT_TRUE(res == PatchErrorCode::SUCCESS);
98
99    Local<ObjectRef> exception = JSNApi::GetAndClearUncaughtException(instance);
100    result = JSNApi::IsQuickFixCausedException(instance, exception, patchFileName);
101    EXPECT_FALSE(result);
102
103    result = JSNApi::Execute(instance, baseFileName, "index");
104    EXPECT_TRUE(result);
105
106    res = JSNApi::UnloadPatch(instance, patchFileName);
107    EXPECT_TRUE(res == PatchErrorCode::SUCCESS);
108
109    res = JSNApi::LoadPatch(instance, patchFileName, baseFileName);
110    EXPECT_TRUE(res == PatchErrorCode::SUCCESS);
111
112    exception = JSNApi::GetAndClearUncaughtException(instance);
113    result = JSNApi::IsQuickFixCausedException(instance, exception, patchFileName);
114    EXPECT_FALSE(result);
115
116    res = JSNApi::UnloadPatch(instance, patchFileName);
117    EXPECT_TRUE(res == PatchErrorCode::SUCCESS);
118}
119
120HWTEST_F_L0(QuickFixTest, HotReload_MultiFile)
121{
122    std::string baseFileName = QUICKFIX_ABC_PATH "multi_file/base/merge.abc";
123    std::string patchFileName = QUICKFIX_ABC_PATH "multi_file/patch/merge.abc";
124
125    JSNApi::EnableUserUncaughtErrorHandler(instance);
126
127    JSNApi::SetBundle(instance, false);
128
129    bool result = JSNApi::Execute(instance, baseFileName, "main");
130    EXPECT_TRUE(result);
131
132    auto res = JSNApi::LoadPatch(instance, patchFileName, baseFileName);
133    EXPECT_TRUE(res == PatchErrorCode::SUCCESS);
134
135    Local<ObjectRef> exception = JSNApi::GetAndClearUncaughtException(instance);
136    result = JSNApi::IsQuickFixCausedException(instance, exception, patchFileName);
137    EXPECT_FALSE(result);
138
139    res = JSNApi::UnloadPatch(instance, patchFileName);
140    EXPECT_TRUE(res == PatchErrorCode::SUCCESS);
141}
142
143HWTEST_F_L0(QuickFixTest, HotReload_Restart_MultiFile)
144{
145    std::string baseFileName = QUICKFIX_ABC_PATH "multi_file/base/merge.abc";
146    std::string patchFileName = QUICKFIX_ABC_PATH "multi_file/patch/merge.abc";
147
148    JSNApi::EnableUserUncaughtErrorHandler(instance);
149
150    JSNApi::SetBundle(instance, false);
151
152    bool result = JSNApi::Execute(instance, baseFileName, "main");
153    EXPECT_TRUE(result);
154
155    auto res = JSNApi::LoadPatch(instance, patchFileName, baseFileName);
156    EXPECT_TRUE(res == PatchErrorCode::SUCCESS);
157
158    Local<ObjectRef> exception = JSNApi::GetAndClearUncaughtException(instance);
159    result = JSNApi::IsQuickFixCausedException(instance, exception, patchFileName);
160    EXPECT_FALSE(result);
161
162    result = JSNApi::Execute(instance, baseFileName, "main");
163    EXPECT_TRUE(result);
164
165    res = JSNApi::UnloadPatch(instance, patchFileName);
166    EXPECT_TRUE(res == PatchErrorCode::SUCCESS);
167
168    res = JSNApi::LoadPatch(instance, patchFileName, baseFileName);
169    EXPECT_TRUE(res == PatchErrorCode::SUCCESS);
170
171    res = JSNApi::UnloadPatch(instance, patchFileName);
172    EXPECT_TRUE(res == PatchErrorCode::SUCCESS);
173}
174
175HWTEST_F_L0(QuickFixTest, HotReload_MultiHap)
176{
177    std::string baseFileName1 = QUICKFIX_ABC_PATH "single_file/base/index.abc";
178    std::string patchFileName1 = QUICKFIX_ABC_PATH "single_file/patch/index.abc";
179
180    std::string baseFileName2 = QUICKFIX_ABC_PATH "multi_file/base/merge.abc";
181    std::string patchFileName2 = QUICKFIX_ABC_PATH "multi_file/patch/merge.abc";
182
183    JSNApi::SetBundle(instance, false);
184
185    bool result = JSNApi::Execute(instance, baseFileName1, "index");
186    EXPECT_TRUE(result);
187
188    result = JSNApi::Execute(instance, baseFileName2, "main");
189    EXPECT_TRUE(result);
190
191    auto res = JSNApi::LoadPatch(instance, patchFileName1, baseFileName1);
192    EXPECT_TRUE(res == PatchErrorCode::SUCCESS);
193
194    res = JSNApi::LoadPatch(instance, patchFileName2, baseFileName2);
195    EXPECT_TRUE(res == PatchErrorCode::SUCCESS);
196
197    res = JSNApi::UnloadPatch(instance, patchFileName1);
198    EXPECT_TRUE(res == PatchErrorCode::SUCCESS);
199
200    res = JSNApi::UnloadPatch(instance, patchFileName2);
201    EXPECT_TRUE(res == PatchErrorCode::SUCCESS);
202}
203
204HWTEST_F_L0(QuickFixTest, HotReload_Buffer)
205{
206    const char *baseFileName = "__base.pa";
207    const char *baseData = R"(
208        .function void foo() {}
209    )";
210    const char *patchFileName = "__patch.pa";
211    const char *patchData = R"(
212        .function void foo() {}
213    )";
214
215    JSPandaFileManager *pfManager = JSPandaFileManager::GetInstance();
216    Parser parser;
217    auto res = parser.Parse(patchData);
218    std::unique_ptr<const File> basePF = pandasm::AsmEmitter::Emit(res.Value());
219    std::unique_ptr<const File> patchPF = pandasm::AsmEmitter::Emit(res.Value());
220    std::shared_ptr<JSPandaFile> baseFile = pfManager->NewJSPandaFile(basePF.release(), CString(baseFileName));
221    std::shared_ptr<JSPandaFile> patchFile = pfManager->NewJSPandaFile(patchPF.release(), CString(patchFileName));
222    pfManager->AddJSPandaFile(baseFile);
223    pfManager->AddJSPandaFile(patchFile);
224
225    auto result = JSNApi::LoadPatch(instance, patchFileName, (uint8_t *)patchData, sizeof(patchData),
226                                    baseFileName, (uint8_t *)baseData, sizeof(baseData));
227    EXPECT_FALSE(result == PatchErrorCode::SUCCESS);
228
229    pfManager->RemoveJSPandaFile(baseFile.get());
230    pfManager->RemoveJSPandaFile(patchFile.get());
231}
232
233HWTEST_F_L0(QuickFixTest, HotReload_Instantiate)
234{
235    ThreadManagedScope managedScope(thread);
236
237    CString baseFileName = QUICKFIX_ABC_PATH "multi_file/base/merge.abc";
238    std::shared_ptr<JSPandaFile> baseFile =
239        JSPandaFileManager::GetInstance()->LoadJSPandaFile(thread, baseFileName, "");
240    EXPECT_TRUE(baseFile != nullptr);
241
242    CString patchFileName = QUICKFIX_ABC_PATH "multi_file/patch/merge.abc";
243    std::shared_ptr<JSPandaFile> patchFile =
244        JSPandaFileManager::GetInstance()->LoadJSPandaFile(thread, patchFileName, "");
245    EXPECT_TRUE(patchFile != nullptr);
246
247    CString replacedRecordName = "main";
248    EcmaContext *context = thread->GetCurrentEcmaContext();
249    context->SetStageOfHotReload(StageOfHotReload::BEGIN_EXECUTE_PATCHMAIN);
250
251    ModuleManager *moduleManager = context->GetModuleManager();
252    JSHandle<JSTaggedValue> module =
253        moduleManager->HostResolveImportedModuleWithMergeForHotReload(patchFileName, replacedRecordName, false);
254    EXPECT_FALSE(module->IsHole());
255
256    JSHandle<Program> program =
257        JSPandaFileManager::GetInstance()->GenerateProgram(instance, patchFile.get(), replacedRecordName);
258    EXPECT_FALSE(program.IsEmpty());
259
260    SourceTextModule::Instantiate(thread, module, false);
261    EXPECT_TRUE(JSHandle<SourceTextModule>::Cast(module)->GetStatus() == ModuleStatus::INSTANTIATED);
262
263    context->SetStageOfHotReload(StageOfHotReload::LOAD_END_EXECUTE_PATCHMAIN);
264    JSHandle<SourceTextModule>::Cast(module)->SetStatus(ModuleStatus::UNINSTANTIATED);
265    SourceTextModule::Instantiate(thread, module, false);
266    EXPECT_TRUE(JSHandle<SourceTextModule>::Cast(module)->GetStatus() == ModuleStatus::INSTANTIATED);
267}
268
269bool QuickFixQueryFunc(
270    std::string baseFileName, std::string &patchFileName, uint8_t ** patchBuffer, size_t &patchBufferSize)
271{
272    if (baseFileName != QUICKFIX_ABC_PATH "multi_file/base/merge.abc") {
273        return false;
274    }
275
276    patchFileName = "__index.pa";
277    const char data[] = R"(
278        .function void foo() {}
279    )";
280
281    Parser parser;
282    auto res = parser.Parse(data);
283    uint8_t *bufferData = reinterpret_cast<uint8_t *>((const_cast<char *>(data)));
284    *patchBuffer = bufferData;
285    patchBufferSize = sizeof(data);
286    return true;
287}
288
289HWTEST_F_L0(QuickFixTest, HotReload_RegisterQuickFixQueryFunc)
290{
291    std::string baseFileName = QUICKFIX_ABC_PATH "multi_file/base/merge.abc";
292    std::string patchFileName = "__index.pa";
293    JSNApi::RegisterQuickFixQueryFunc(instance, QuickFixQueryFunc);
294
295    std::shared_ptr<JSPandaFile> baseFile =
296        JSPandaFileManager::GetInstance()->LoadJSPandaFile(thread, baseFileName.c_str(), "");
297    EXPECT_TRUE(baseFile != nullptr);
298    std::shared_ptr<JSPandaFile> patchFile =
299        JSPandaFileManager::GetInstance()->FindJSPandaFile(patchFileName.c_str());
300    EXPECT_TRUE(patchFile == nullptr);
301
302    QuickFixManager *quickFixManager = instance->GetQuickFixManager();
303    quickFixManager->LoadPatchIfNeeded(thread, baseFile.get());
304
305    JSPandaFileManager *pfManager = JSPandaFileManager::GetInstance();
306    pfManager->RemoveJSPandaFile(baseFile.get());
307}
308}  // namespace panda::test
309