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 WaitForDropframe()63 static bool WaitForDropframe() 64 { 65 auto predicate = []() REQUIRES(eventMutex_) { return lastEvent_ == DebugEvent::DROPFRAME; }; 66 return WaitForEvent(DebugEvent::DROPFRAME, predicate, [] {}); 67 } 68 WaitForCheckComplete()69 static bool WaitForCheckComplete() 70 { 71 auto predicate = []() REQUIRES(eventMutex_) { return lastEvent_ == DebugEvent::CHECK_COMPLETE; }; 72 return WaitForEvent(DebugEvent::CHECK_COMPLETE, predicate, [] {}); 73 } 74 WaitForExit()75 static bool WaitForExit() 76 { 77 return WaitForEvent(DebugEvent::VM_DEATH, 78 []() REQUIRES(eventMutex_) { 79 return lastEvent_ == DebugEvent::VM_DEATH; 80 }, [] {}); 81 } 82 WaitForStepComplete(JSPtLocation location)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 WaitForException()94 static bool WaitForException() 95 { 96 auto predicate = []() REQUIRES(eventMutex_) { return lastEvent_ == DebugEvent::EXCEPTION; }; 97 return WaitForEvent(DebugEvent::EXCEPTION, predicate, [] {}); 98 } 99 WaitForInit()100 static bool WaitForInit() 101 { 102 return WaitForEvent(DebugEvent::VM_START, 103 []() REQUIRES(eventMutex_) { 104 return initialized_; 105 }, [] {}); 106 } 107 WaitForLoadModule()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 Reset()126 static void Reset() 127 { 128 os::memory::LockHolder lock(eventMutex_); 129 initialized_ = false; 130 lastEvent_ = DebugEvent::VM_START; 131 } 132 GetTests()133 static TestMap &GetTests() 134 { 135 return testMap_; 136 } 137 IsTestFinished()138 static bool IsTestFinished() 139 { 140 os::memory::LockHolder lock(eventMutex_); 141 return lastEvent_ == DebugEvent::VM_DEATH; 142 } 143 GetLocation(const char * sourceFile,int32_t line,int32_t column,const char * pandaFile)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 GetSourceLocation(const JSPtLocation & location,const char * pandaFile)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 Continue()188 static bool Continue() 189 { 190 os::memory::LockHolder lock(suspendMutex_); 191 suspended_ = false; 192 suspendCv_.Signal(); 193 return true; 194 } 195 196 private: 197 template<class Predicate, class OnSuccessAction> WaitForEvent(DebugEvent event,Predicate predicate,OnSuccessAction action)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 227 std::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