• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2021-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 #ifndef PANDA_RUNTIME_DEBUG_TEST_TEST_UTIL_H
16 #define PANDA_RUNTIME_DEBUG_TEST_TEST_UTIL_H
17 
18 #include <cstdlib>
19 #include <climits>
20 #include <unordered_set>
21 #include <unordered_map>
22 #include <string>
23 
24 #include "runtime/tests/tooling/api_test.h"
25 #include "runtime/tests/tooling/test_extractor.h"
26 #include "runtime/include/mtmanaged_thread.h"
27 #include "runtime/include/tooling/pt_location.h"
28 #include "runtime/include/tooling/pt_thread.h"
29 #include "libpandabase/os/mutex.h"
30 
31 namespace ark::tooling::test {
32 using TestMap = std::unordered_map<panda_file::SourceLang, std::unordered_map<const char *, std::unique_ptr<ApiTest>>>;
33 
34 const char *GetCurrentTestName();
35 
36 class TestUtil {
37 public:
RegisterTest(panda_file::SourceLang language,const char * testName,std::unique_ptr<ApiTest> test)38     static void RegisterTest(panda_file::SourceLang language, const char *testName, std::unique_ptr<ApiTest> test)
39     {
40         auto it = testMap_.find(language);
41         if (it == testMap_.end()) {
42             std::unordered_map<const char *, std::unique_ptr<ApiTest>> entry;
43             auto res = testMap_.emplace(language, std::move(entry));
44             it = res.first;
45         }
46         it->second.insert({testName, std::move(test)});
47     }
48 
SetExtractorFactory(TestExtractorFactory * factory)49     static void SetExtractorFactory(TestExtractorFactory *factory)
50     {
51         extractorFactory_ = factory;
52     }
53 
GetTest(const char * name)54     static ApiTest *GetTest(const char *name)
55     {
56         for (const auto &[lang, internal_map] : testMap_) {
57             (void)lang;
58             auto internalIt = std::find_if(internal_map.begin(), internal_map.end(),
59                                            [name](auto &iterator) { return !::strcmp(iterator.first, name); });
60             if (internalIt != internal_map.end()) {
61                 return internalIt->second.get();
62             }
63         }
64         LOG(FATAL, DEBUGGER) << "Test " << name << " not found";
65         return nullptr;
66     }
67 
WaitForBreakpoint(PtLocation location)68     static PtThread WaitForBreakpoint(PtLocation location)
69     {
70         PtThread stoppedThread(PtThread::NONE);
71         auto predicate = [&location]() REQUIRES(eventMutex_) { return lastEventLocation_ == location; };
72         auto onSuccess = [&stoppedThread]() REQUIRES(eventMutex_) {
73             stoppedThread = lastEventThread_;
74 
75             // Need to reset location, because we might want to stop at the same point
76             lastEventLocation_ = PtLocation("", EntityId(0), 0);
77         };
78 
79         WaitForEvent(DebugEvent::BREAKPOINT, predicate, onSuccess);
80         return stoppedThread;
81     }
82 
WaitForExit()83     static bool WaitForExit()
84     {
85         return WaitForEvent(
86             DebugEvent::VM_DEATH, []() REQUIRES(eventMutex_) { return lastEvent_ == DebugEvent::VM_DEATH; }, [] {});
87     }
88 
WaitForInit()89     static bool WaitForInit()
90     {
91         return WaitForEvent(
92             DebugEvent::VM_INITIALIZATION, []() REQUIRES(eventMutex_) { return initialized_; }, [] {});
93     }
94 
95     static void Event(DebugEvent event, PtThread thread = PtThread::NONE,
96                       PtLocation location = PtLocation("", EntityId(0), 0))
97     {
98         LOG(DEBUG, DEBUGGER) << "Occured event " << event << " in thread with id " << thread.GetId();
99         os::memory::LockHolder holder(eventMutex_);
100         lastEvent_ = event;
101         lastEventThread_ = thread;
102         lastEventLocation_ = location;
103         if (event == DebugEvent::VM_INITIALIZATION) {
104             initialized_ = true;
105         }
106         eventCv_.Signal();
107     }
108 
Reset()109     static void Reset()
110     {
111         os::memory::LockHolder lock(eventMutex_);
112         initialized_ = false;
113         lastEvent_ = DebugEvent::VM_START;
114     }
115 
GetTests()116     static TestMap &GetTests()
117     {
118         return testMap_;
119     }
120 
IsTestFinished()121     static bool IsTestFinished()
122     {
123         os::memory::LockHolder lock(eventMutex_);
124         return lastEvent_ == DebugEvent::VM_DEATH;
125     }
126 
GetLocation(const char * sourceFile,uint32_t line,const char * pandaFile)127     static PtLocation GetLocation(const char *sourceFile, uint32_t line, const char *pandaFile)
128     {
129         std::unique_ptr<const panda_file::File> uFile = panda_file::File::Open(pandaFile);
130         const panda_file::File *pf = uFile.get();
131         if (pf == nullptr) {
132             return PtLocation("", EntityId(0), 0);
133         }
134 
135         auto extractor = extractorFactory_->MakeTestExtractor(pf);
136         auto [id, offset] = extractor->GetBreakpointAddress({sourceFile, line});
137         return PtLocation(pandaFile, id, offset);
138     }
139 
140     static std::vector<panda_file::LocalVariableInfo> GetVariables(Method *method, uint32_t offset);
141 
142     static int32_t GetValueRegister(Method *method, const char *varName, uint32_t offset = 0);
143 
SuspendUntilContinue(DebugEvent reason,PtThread thread,PtLocation location)144     static bool SuspendUntilContinue(DebugEvent reason, PtThread thread, PtLocation location)
145     {
146         {
147             os::memory::LockHolder lock(suspendMutex_);
148             suspended_ = true;
149         }
150 
151         // Notify the debugger thread about the suspend event
152         Event(reason, thread, location);
153 
154         // Wait for continue
155         {
156             os::memory::LockHolder lock(suspendMutex_);
157             while (suspended_) {
158                 suspendCv_.Wait(&suspendMutex_);
159             }
160         }
161 
162         return true;
163     }
164 
Continue()165     static bool Continue()
166     {
167         os::memory::LockHolder lock(suspendMutex_);
168         suspended_ = false;
169         suspendCv_.Signal();
170         return true;
171     }
172 
GetUserThreadList(DebugInterface * debugInterface,PandaVector<PtThread> * threadList)173     static bool GetUserThreadList(DebugInterface *debugInterface, PandaVector<PtThread> *threadList)
174     {
175         PandaVector<PtThread> threads;
176         debugInterface->GetThreadList(&threads);
177 
178         for (auto &thread : threads) {
179             ManagedThread *managedThread = thread.GetManagedThread();
180             if (MTManagedThread::ThreadIsMTManagedThread(managedThread)) {
181                 auto mtManagedThread = MTManagedThread::CastFromThread(managedThread);
182                 if (mtManagedThread->IsDaemon()) {
183                     continue;
184                 }
185             }
186             threadList->push_back(thread);
187         }
188         return true;
189     }
190 
191 private:
192     template <class Predicate, class OnSuccessAction>
WaitForEvent(DebugEvent event,Predicate predicate,OnSuccessAction action)193     static bool WaitForEvent(DebugEvent event, Predicate predicate, OnSuccessAction action)
194     {
195         os::memory::LockHolder holder(eventMutex_);
196         while (!predicate()) {
197             if (lastEvent_ == DebugEvent::VM_DEATH) {
198                 return false;
199             }
200             constexpr uint64_t TIMEOUT_MSEC = 100000U;
201             bool timeExceeded = eventCv_.TimedWait(&eventMutex_, TIMEOUT_MSEC);
202             if (timeExceeded) {
203                 LOG(FATAL, DEBUGGER) << "Time limit exceeded while waiting " << event;
204                 return false;
205             }
206         }
207         action();
208         return true;
209     }
210 
211     static TestMap testMap_;
212     static os::memory::Mutex eventMutex_;
213     static os::memory::ConditionVariable eventCv_ GUARDED_BY(eventMutex_);
214     static DebugEvent lastEvent_ GUARDED_BY(eventMutex_);
215     static PtThread lastEventThread_ GUARDED_BY(eventMutex_);
216     static PtLocation lastEventLocation_ GUARDED_BY(eventMutex_);
217     static os::memory::Mutex suspendMutex_;
218     static os::memory::ConditionVariable suspendCv_ GUARDED_BY(suspendMutex_);
219     static bool suspended_ GUARDED_BY(suspendMutex_);
220     static bool initialized_ GUARDED_BY(eventMutex_);
221     static TestExtractorFactory *extractorFactory_;
222 };
223 
224 // Some toolchains have << overloading for std::nullptr_t
225 // NOTE(asoldatov): Find a better workaround, distro-specifc define seems too intrusive.
226 #if (!defined PANDA_TARGET_MOBILE) && (!defined PANDA_TARGET_LINUX_UBUNTU_20_04)
227 std::ostream &operator<<(std::ostream &out, std::nullptr_t);
228 #endif
229 
230 // NOLINTBEGIN(cppcoreguidelines-macro-usage)
231 #define ASSERT_FAIL_IMPL(val1, val2, strval1, strval2, msg)                            \
232     std::cerr << "Assertion failed at " << __FILE__ << ':' << __LINE__ << std::endl;   \
233     std::cerr << "Expected that " strval1 " is " << (msg) << " " strval2 << std::endl; \
234     std::cerr << "\t" strval1 ": " << (val1) << std::endl;                             \
235     std::cerr << "\t" strval2 ": " << (val2) << std::endl;                             \
236     std::abort()
237 
238 #define ASSERT_TRUE(cond)                                           \
239     do {                                                            \
240         auto res = (cond);                                          \
241         if (!res) {                                                 \
242             ASSERT_FAIL_IMPL(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_IMPL(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_IMPL(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_IMPL(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_IMPL(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_IMPL(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_IMPL(TestUtil::IsTestFinished(), true, "TestUtil::IsTestFinished()", "true", ""); \
294         }                                                                                                 \
295     } while (0)
296 
297 #define ASSERT_LOCATION_EQ(lhs, rhs)                                                 \
298     do {                                                                             \
299         ASSERT_STREQ((lhs).GetPandaFile(), (rhs).GetPandaFile());                    \
300         ASSERT_EQ((lhs).GetMethodId().GetOffset(), (rhs).GetMethodId().GetOffset()); \
301         ASSERT_EQ((lhs).GetBytecodeOffset(), (rhs).GetBytecodeOffset());             \
302     } while (0)
303 
304 #define ASSERT_THREAD_VALID(thread)                          \
305     do {                                                     \
306         ASSERT_NE((thread).GetId(), PtThread::NONE.GetId()); \
307     } while (0)
308 
309 #define ASSERT_BREAKPOINT_SUCCESS(location)                         \
310     do {                                                            \
311         PtThread suspended = TestUtil::WaitForBreakpoint(location); \
312         ASSERT_THREAD_VALID(suspended);                             \
313     } while (0)
314 // NOLINTEND(cppcoreguidelines-macro-usage)
315 
316 }  // namespace ark::tooling::test
317 
318 #endif  // PANDA_RUNTIME_DEBUG_TEST_TEST_UTIL_H
319