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