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