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