1/*
2 * Copyright (c) 2021 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 ECMASCRIPT_TOOLING_TEST_UTILS_TEST_UTIL_H
17#define ECMASCRIPT_TOOLING_TEST_UTILS_TEST_UTIL_H
18
19#include "test/utils/test_events.h"
20#include "test/utils/test_extractor.h"
21
22#include "agent/debugger_impl.h"
23
24#include "ecmascript/jspandafile/js_pandafile_manager.h"
25#include "ecmascript/debugger/js_debugger.h"
26#include "os/mutex.h"
27
28namespace panda::ecmascript::tooling::test {
29template<class Key, class T, class Hash = std::hash<Key>, class KeyEqual = std::equal_to<Key>>
30using CUnorderedMap = panda::ecmascript::CUnorderedMap<Key, T, Hash, KeyEqual>;
31using TestMap = CUnorderedMap<std::string, std::unique_ptr<TestEvents>>;
32
33class TestUtil {
34public:
35    static void RegisterTest(const std::string &testName, std::unique_ptr<TestEvents> test)
36    {
37        testMap_.insert({testName, std::move(test)});
38    }
39
40    static TestEvents *GetTest(const std::string &name)
41    {
42        auto iter = std::find_if(testMap_.begin(), testMap_.end(), [&name](auto &it) {
43            return it.first == name;
44        });
45        if (iter != testMap_.end()) {
46            return iter->second.get();
47        }
48        LOG_DEBUGGER(FATAL) << "Test " << name << " not found";
49        return nullptr;
50    }
51
52    static void WaitForBreakpoint(JSPtLocation location)
53    {
54        auto predicate = [&location]() REQUIRES(eventMutex_) { return lastEventLocation_ == location; };
55        auto onSuccess = []() REQUIRES(eventMutex_) {
56            // Need to reset location, because we might want to stop at the same point
57            lastEventLocation_ = JSPtLocation(nullptr, EntityId(0), 0);
58        };
59
60        WaitForEvent(DebugEvent::BREAKPOINT, predicate, onSuccess);
61    }
62
63    static bool WaitForDropframe()
64    {
65        auto predicate = []() REQUIRES(eventMutex_) { return lastEvent_ == DebugEvent::DROPFRAME; };
66        return WaitForEvent(DebugEvent::DROPFRAME, predicate, [] {});
67    }
68
69    static bool WaitForCheckComplete()
70    {
71        auto predicate = []() REQUIRES(eventMutex_) { return lastEvent_ == DebugEvent::CHECK_COMPLETE; };
72        return WaitForEvent(DebugEvent::CHECK_COMPLETE, predicate, [] {});
73    }
74
75    static bool WaitForExit()
76    {
77        return WaitForEvent(DebugEvent::VM_DEATH,
78            []() REQUIRES(eventMutex_) {
79                return lastEvent_ == DebugEvent::VM_DEATH;
80            }, [] {});
81    }
82
83    static void WaitForStepComplete(JSPtLocation location)
84    {
85        auto predicate = [&location]() REQUIRES(eventMutex_) { return lastEventLocation_ == location; };
86        auto onSuccess = []() REQUIRES(eventMutex_) {
87            // Need to reset location, because we might want to stop at the same point
88            lastEventLocation_ = JSPtLocation(nullptr, EntityId(0), 0);
89        };
90
91        WaitForEvent(DebugEvent::STEP_COMPLETE, predicate, onSuccess);
92    }
93
94    static bool WaitForException()
95    {
96        auto predicate = []() REQUIRES(eventMutex_) { return lastEvent_ == DebugEvent::EXCEPTION; };
97        return WaitForEvent(DebugEvent::EXCEPTION, predicate, [] {});
98    }
99
100    static bool WaitForInit()
101    {
102        return WaitForEvent(DebugEvent::VM_START,
103            []() REQUIRES(eventMutex_) {
104                return initialized_;
105            }, [] {});
106    }
107
108    static bool WaitForLoadModule()
109    {
110        auto predicate = []() REQUIRES(eventMutex_) { return lastEvent_ == DebugEvent::LOAD_MODULE; };
111        return WaitForEvent(DebugEvent::LOAD_MODULE, predicate, [] {});
112    }
113
114    static void Event(DebugEvent event, JSPtLocation location = JSPtLocation(nullptr, EntityId(0), 0))
115    {
116        LOG_DEBUGGER(DEBUG) << "Occurred event " << event;
117        os::memory::LockHolder holder(eventMutex_);
118        lastEvent_ = event;
119        lastEventLocation_ = location;
120        if (event == DebugEvent::VM_START) {
121            initialized_ = true;
122        }
123        eventCv_.Signal();
124    }
125
126    static void Reset()
127    {
128        os::memory::LockHolder lock(eventMutex_);
129        initialized_ = false;
130        lastEvent_ = DebugEvent::VM_START;
131    }
132
133    static TestMap &GetTests()
134    {
135        return testMap_;
136    }
137
138    static bool IsTestFinished()
139    {
140        os::memory::LockHolder lock(eventMutex_);
141        return lastEvent_ == DebugEvent::VM_DEATH;
142    }
143
144    static JSPtLocation GetLocation(const char *sourceFile, int32_t line, int32_t column, const char *pandaFile)
145    {
146        auto jsPandaFile = ::panda::ecmascript::JSPandaFileManager::GetInstance()->FindJSPandaFile(pandaFile);
147        if (jsPandaFile == nullptr) {
148            LOG_DEBUGGER(FATAL) << "cannot find: " << pandaFile;
149            UNREACHABLE();
150        }
151        TestExtractor extractor(jsPandaFile.get());
152        auto [id, offset] = extractor.GetBreakpointAddress({jsPandaFile.get(), line, column});
153        return JSPtLocation(jsPandaFile.get(), id, offset, sourceFile);
154    }
155
156    static SourceLocation GetSourceLocation(const JSPtLocation &location, const char *pandaFile)
157    {
158        auto jsPandaFile = ::panda::ecmascript::JSPandaFileManager::GetInstance()->FindJSPandaFile(pandaFile);
159        if (jsPandaFile == nullptr) {
160            LOG_DEBUGGER(FATAL) << "cannot find: " << pandaFile;
161            UNREACHABLE();
162        }
163        TestExtractor extractor(jsPandaFile.get());
164        return extractor.GetSourceLocation(jsPandaFile.get(), location.GetMethodId(), location.GetBytecodeOffset());
165    }
166
167    static bool SuspendUntilContinue(DebugEvent reason, JSPtLocation location = JSPtLocation(nullptr, EntityId(0), 0))
168    {
169        os::memory::LockHolder lock(suspendMutex_);
170        suspended_ = true;
171
172        // Notify the debugger thread about the suspend event
173        Event(reason, location);
174
175        // Wait for continue
176        while (suspended_) {
177            constexpr uint64_t TIMEOUT_MSEC = 300000U;
178            bool timeExceeded = suspendCv_.TimedWait(&suspendMutex_, TIMEOUT_MSEC);
179            if (timeExceeded) {
180                LOG_DEBUGGER(FATAL) << "Time limit exceeded while suspend";
181                return false;
182            }
183        }
184
185        return true;
186    }
187
188    static bool Continue()
189    {
190        os::memory::LockHolder lock(suspendMutex_);
191        suspended_ = false;
192        suspendCv_.Signal();
193        return true;
194    }
195
196private:
197    template<class Predicate, class OnSuccessAction>
198    static bool WaitForEvent(DebugEvent event, Predicate predicate, OnSuccessAction action)
199    {
200        os::memory::LockHolder holder(eventMutex_);
201        while (!predicate()) {
202            if (lastEvent_ == DebugEvent::VM_DEATH) {
203                return false;
204            }
205            constexpr uint64_t TIMEOUT_MSEC = 300000U;
206            bool timeExceeded = eventCv_.TimedWait(&eventMutex_, TIMEOUT_MSEC);
207            if (timeExceeded) {
208                LOG_DEBUGGER(FATAL) << "Time limit exceeded while waiting " << event;
209                return false;
210            }
211        }
212        action();
213        return true;
214    }
215
216    static TestMap testMap_;
217    static os::memory::Mutex eventMutex_;
218    static os::memory::ConditionVariable eventCv_ GUARDED_BY(eventMutex_);
219    static DebugEvent lastEvent_ GUARDED_BY(eventMutex_);
220    static JSPtLocation lastEventLocation_ GUARDED_BY(eventMutex_);
221    static os::memory::Mutex suspendMutex_;
222    static os::memory::ConditionVariable suspendCv_ GUARDED_BY(suspendMutex_);
223    static bool suspended_ GUARDED_BY(suspendMutex_);
224    static bool initialized_ GUARDED_BY(eventMutex_);
225};
226
227std::ostream &operator<<(std::ostream &out, std::nullptr_t);
228
229#define ASSERT_FAIL_(val1, val2, strval1, strval2, msg)                                    \
230    do {                                                                                   \
231        std::cerr << "Assertion failed at " << __FILE__ << ':' << __LINE__ << std::endl;   \
232        std::cerr << "Expected that " strval1 " is " << (msg) << " " strval2 << std::endl; \
233        std::cerr << "\t" strval1 ": " << (val1) << std::endl;                             \
234        std::cerr << "\t" strval2 ": " << (val2) << std::endl;                             \
235        std::abort();                                                                      \
236    } while (0)
237
238#define ASSERT_TRUE(cond)                                       \
239    do {                                                        \
240        auto res = (cond);                                      \
241        if (!res) {                                             \
242            ASSERT_FAIL_(res, true, #cond, "true", "equal to"); \
243        }                                                       \
244    } while (0)
245
246#define ASSERT_FALSE(cond)                                        \
247    do {                                                          \
248        auto res = (cond);                                        \
249        if (res) {                                                \
250            ASSERT_FAIL_(res, false, #cond, "false", "equal to"); \
251        }                                                         \
252    } while (0)
253
254#define ASSERT_EQ(lhs, rhs)                                   \
255    do {                                                      \
256        auto res1 = (lhs);                                    \
257        decltype(res1) res2 = (rhs);                          \
258        if (res1 != res2) {                                   \
259            ASSERT_FAIL_(res1, res2, #lhs, #rhs, "equal to"); \
260        }                                                     \
261    } while (0)
262
263#define ASSERT_NE(lhs, rhs)                                       \
264    do {                                                          \
265        auto res1 = (lhs);                                        \
266        decltype(res1) res2 = (rhs);                              \
267        if (res1 == res2) {                                       \
268            ASSERT_FAIL_(res1, res2, #lhs, #rhs, "not equal to"); \
269        }                                                         \
270    } while (0)
271
272#define ASSERT_STREQ(lhs, rhs)                                \
273    do {                                                      \
274        auto res1 = (lhs);                                    \
275        decltype(res1) res2 = (rhs);                          \
276        if (::strcmp(res1, res2) != 0) {                      \
277            ASSERT_FAIL_(res1, res2, #lhs, #rhs, "equal to"); \
278        }                                                     \
279    } while (0)
280
281#define ASSERT_SUCCESS(api_call)                                                                    \
282    do {                                                                                            \
283        auto error = api_call;                                                                      \
284        if (error) {                                                                                \
285            ASSERT_FAIL_(error.value().GetMessage(), "Success", "API call result", "Expected", ""); \
286        }                                                                                           \
287    } while (0)
288
289#define ASSERT_EXITED()                                                                               \
290    do {                                                                                              \
291        bool res = TestUtil::WaitForExit();                                                           \
292        if (!res) {                                                                                   \
293            ASSERT_FAIL_(TestUtil::IsTestFinished(), true, "TestUtil::IsTestFinished()", "true", ""); \
294        }                                                                                             \
295    } while (0)
296
297#define ASSERT_LOCATION_EQ(lhs, rhs)                                                 \
298    do {                                                                             \
299        ASSERT_EQ((lhs).GetJsPandaFile(), (rhs).GetJsPandaFile());                   \
300        ASSERT_EQ((lhs).GetMethodId().GetOffset(), (rhs).GetMethodId().GetOffset()); \
301        ASSERT_EQ((lhs).GetBytecodeOffset(), (rhs).GetBytecodeOffset());             \
302    } while (0)
303}  // namespace panda::ecmascript::tooling::test
304
305#endif  // ECMASCRIPT_TOOLING_TEST_UTILS_TEST_UTIL_H
306