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