File indexing completed on 2024-12-08 04:27:18
0001 /* 0002 SPDX-FileCopyrightText: 2019 Nicolas Carion 0003 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0004 */ 0005 0006 #include "logger.hpp" 0007 #include "bin/projectitemmodel.h" 0008 #include "timeline2/model/timelinefunctions.hpp" 0009 #include "timeline2/model/timelineitemmodel.hpp" 0010 #include "timeline2/model/timelinemodel.hpp" 0011 #include <QString> 0012 #include <fstream> 0013 #include <iomanip> 0014 #include <iostream> 0015 #include <sstream> 0016 #pragma GCC diagnostic push 0017 #pragma GCC diagnostic ignored "-Wunused-parameter" 0018 #pragma GCC diagnostic ignored "-Wsign-conversion" 0019 #pragma GCC diagnostic ignored "-Wfloat-equal" 0020 #pragma GCC diagnostic ignored "-Wshadow" 0021 #pragma GCC diagnostic ignored "-Wpedantic" 0022 #include <rttr/registration> 0023 #pragma GCC diagnostic pop 0024 0025 thread_local bool Logger::is_executing = false; 0026 std::mutex Logger::mut; 0027 std::vector<rttr::variant> Logger::operations; 0028 std::vector<Logger::Invok> Logger::invoks; 0029 std::unordered_map<std::string, std::vector<Logger::Constr>> Logger::constr; 0030 std::unordered_map<std::string, std::string> Logger::translation_table; 0031 std::unordered_map<std::string, std::string> Logger::back_translation_table; 0032 int Logger::dump_count = 0; 0033 0034 thread_local size_t Logger::result_awaiting = INT_MAX; 0035 0036 void Logger::init() 0037 { 0038 std::string cur_ind = "a"; 0039 auto incr_ind = [&](auto &&self, size_t i = 0) { 0040 if (i >= cur_ind.size()) { 0041 cur_ind += "a"; 0042 return; 0043 } 0044 if (cur_ind[i] == 'z') { 0045 cur_ind[i] = 'A'; 0046 } else if (cur_ind[i] == 'Z') { 0047 cur_ind[i] = 'a'; 0048 self(self, i + 1); 0049 } else { 0050 cur_ind[i]++; 0051 } 0052 if (cur_ind == "u" || cur_ind == "r") { 0053 // reserved for undo and redo 0054 self(self, i); 0055 } 0056 }; 0057 0058 for (const auto &o : {"TimelineModel", "TrackModel", "test_producer", "test_producer_sound", "ClipModel"}) { 0059 translation_table[std::string("constr_") + o] = cur_ind; 0060 incr_ind(incr_ind); 0061 } 0062 0063 for (const auto &m : rttr::type::get<TimelineModel>().get_methods()) { 0064 translation_table[m.get_name().to_string()] = cur_ind; 0065 incr_ind(incr_ind); 0066 } 0067 0068 for (const auto &m : rttr::type::get<TimelineFunctions>().get_methods()) { 0069 translation_table[m.get_name().to_string()] = cur_ind; 0070 incr_ind(incr_ind); 0071 } 0072 0073 for (const auto &i : translation_table) { 0074 back_translation_table[i.second] = i.first; 0075 } 0076 } 0077 0078 bool Logger::start_logging() 0079 { 0080 std::unique_lock<std::mutex> lk(mut); 0081 if (is_executing) { 0082 return false; 0083 } 0084 is_executing = true; 0085 return true; 0086 } 0087 void Logger::stop_logging() 0088 { 0089 std::unique_lock<std::mutex> lk(mut); 0090 is_executing = false; 0091 } 0092 std::string Logger::get_ptr_name(const rttr::variant &ptr) 0093 { 0094 if (ptr.can_convert<TimelineModel *>()) { 0095 return "timeline_" + std::to_string(get_id_from_ptr(ptr.convert<TimelineModel *>())); 0096 } else if (ptr.can_convert<ProjectItemModel *>()) { 0097 return "binModel"; 0098 } else if (ptr.can_convert<TimelineItemModel *>()) { 0099 return "timeline_" + std::to_string(get_id_from_ptr(static_cast<TimelineModel *>(ptr.convert<TimelineItemModel *>()))); 0100 } else { 0101 std::cout << "Error: unhandled ptr type " << ptr.get_type().get_name().to_string() << std::endl; 0102 } 0103 return "unknown"; 0104 } 0105 0106 void Logger::log_res(rttr::variant result) 0107 { 0108 std::unique_lock<std::mutex> lk(mut); 0109 Q_ASSERT(result_awaiting < invoks.size()); 0110 invoks[result_awaiting].res = std::move(result); 0111 } 0112 0113 void Logger::log_create_producer(const std::string &type, std::vector<rttr::variant> args) 0114 { 0115 std::unique_lock<std::mutex> lk(mut); 0116 for (auto &a : args) { 0117 // this will rewove shared/weak/unique ptrs 0118 if (a.get_type().is_wrapper()) { 0119 a = a.extract_wrapped_value(); 0120 } 0121 const std::string class_name = a.get_type().get_name().to_string(); 0122 } 0123 constr[type].push_back({type, std::move(args)}); 0124 operations.emplace_back(ConstrId{type, constr[type].size() - 1}); 0125 } 0126 0127 namespace { 0128 bool isIthParamARef(const rttr::method &method, size_t i) 0129 { 0130 QString sig = QString::fromStdString(method.get_signature().to_string()); 0131 int deb = sig.indexOf("("); 0132 int end = sig.lastIndexOf(")"); 0133 sig = sig.mid(deb + 1, deb - end - 1); 0134 QStringList args = sig.split(QStringLiteral(",")); 0135 return args[(int)i].contains("&") && !args[(int)i].contains("const &"); 0136 } 0137 std::string quoted(const std::string &input) 0138 { 0139 #if __cpp_lib_quoted_string_io 0140 std::stringstream ss; 0141 ss << std::quoted(input); 0142 return ss.str(); 0143 #else 0144 // very incomplete implem 0145 return "\"" + input + "\""; 0146 #endif 0147 } 0148 } // namespace 0149 0150 void Logger::print_trace() 0151 { 0152 dump_count++; 0153 auto process_args = [&](const std::vector<rttr::variant> &args, const std::unordered_set<size_t> &refs = {}) { 0154 std::stringstream ss; 0155 bool deb = true; 0156 size_t i = 0; 0157 for (const auto &a : args) { 0158 if (deb) { 0159 deb = false; 0160 i = 0; 0161 } else { 0162 ss << ", "; 0163 ++i; 0164 } 0165 if (refs.count(i) > 0) { 0166 ss << "dummy_" << i; 0167 } else if (a.get_type() == rttr::type::get<int>()) { 0168 ss << a.convert<int>(); 0169 } else if (a.get_type() == rttr::type::get<double>()) { 0170 ss << a.convert<double>(); 0171 } else if (a.get_type() == rttr::type::get<float>()) { 0172 ss << a.convert<float>(); 0173 } else if (a.get_type() == rttr::type::get<size_t>()) { 0174 ss << a.convert<size_t>(); 0175 } else if (a.get_type() == rttr::type::get<bool>()) { 0176 ss << (a.convert<bool>() ? "true" : "false"); 0177 } else if (a.get_type().is_enumeration()) { 0178 auto e = a.get_type().get_enumeration(); 0179 ss << e.get_name().to_string() << "::" << a.convert<std::string>(); 0180 } else if (a.can_convert<QString>()) { 0181 ss << quoted(a.convert<QString>().toStdString()); 0182 } else if (a.can_convert<std::string>()) { 0183 ss << quoted(a.convert<std::string>()); 0184 } else if (a.can_convert<std::unordered_set<int>>()) { 0185 auto set = a.convert<std::unordered_set<int>>(); 0186 ss << "{"; 0187 bool beg = true; 0188 for (int s : set) { 0189 if (beg) 0190 beg = false; 0191 else 0192 ss << ", "; 0193 ss << s; 0194 } 0195 ss << "}"; 0196 } else if (a.get_type().is_pointer()) { 0197 ss << get_ptr_name(a); 0198 } else { 0199 std::cout << "Error: unhandled arg type " << a.get_type().get_name().to_string() << std::endl; 0200 } 0201 } 0202 return ss.str(); 0203 }; 0204 auto process_args_fuzz = [&](const std::vector<rttr::variant> &args, const std::unordered_set<size_t> &refs = {}) { 0205 std::stringstream ss; 0206 bool deb = true; 0207 size_t i = 0; 0208 for (const auto &a : args) { 0209 if (deb) { 0210 deb = false; 0211 i = 0; 0212 } else { 0213 ss << " "; 0214 ++i; 0215 } 0216 if (refs.count(i) > 0) { 0217 continue; 0218 } else if (a.get_type() == rttr::type::get<int>()) { 0219 ss << a.convert<int>(); 0220 } else if (a.get_type() == rttr::type::get<double>()) { 0221 ss << a.convert<double>(); 0222 } else if (a.get_type() == rttr::type::get<float>()) { 0223 ss << a.convert<float>(); 0224 } else if (a.get_type() == rttr::type::get<size_t>()) { 0225 ss << a.convert<size_t>(); 0226 } else if (a.get_type() == rttr::type::get<bool>()) { 0227 ss << (a.convert<bool>() ? "1" : "0"); 0228 } else if (a.get_type().is_enumeration()) { 0229 ss << a.convert<int>(); 0230 } else if (a.can_convert<QString>()) { 0231 std::string out = a.convert<QString>().toStdString(); 0232 if (out.empty()) { 0233 out = "$$"; 0234 } 0235 ss << out; 0236 } else if (a.can_convert<std::string>()) { 0237 std::string out = a.convert<std::string>(); 0238 if (out.empty()) { 0239 out = "$$"; 0240 } 0241 ss << out; 0242 } else if (a.can_convert<std::unordered_set<int>>()) { 0243 auto set = a.convert<std::unordered_set<int>>(); 0244 ss << set.size() << " "; 0245 bool beg = true; 0246 for (int s : set) { 0247 if (beg) 0248 beg = false; 0249 else 0250 ss << " "; 0251 ss << s; 0252 } 0253 } else if (a.get_type().is_pointer()) { 0254 if (a.can_convert<TimelineModel *>()) { 0255 ss << get_id_from_ptr(a.convert<TimelineModel *>()); 0256 } else if (a.can_convert<TimelineItemModel *>()) { 0257 ss << get_id_from_ptr(static_cast<TimelineModel *>(a.convert<TimelineItemModel *>())); 0258 } else if (a.can_convert<ProjectItemModel *>()) { 0259 // only one binModel, we skip the parameter since it's unambiguous 0260 } else { 0261 std::cout << "Error: unhandled ptr type " << a.get_type().get_name().to_string() << std::endl; 0262 } 0263 } else { 0264 std::cout << "Error: unhandled arg type " << a.get_type().get_name().to_string() << std::endl; 0265 } 0266 } 0267 return ss.str(); 0268 }; 0269 std::ofstream fuzz_file; 0270 fuzz_file.open("fuzz_case_" + std::to_string(dump_count) + ".txt"); 0271 std::ofstream test_file; 0272 test_file.open("test_case_" + std::to_string(dump_count) + ".cpp"); 0273 test_file << "TEST_CASE(\"Regression\") {" << std::endl; 0274 test_file << "auto binModel = pCore->projectItemModel();" << std::endl; 0275 test_file << "binModel->clean();" << std::endl; 0276 test_file << "std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);" << std::endl; 0277 test_file << "std::shared_ptr<MarkerListModel> guideModel = std::make_shared<MarkerListModel>(undoStack);" << std::endl; 0278 test_file << "KdenliveDoc::next_id = 0;" << std::endl; 0279 test_file << "{" << std::endl; 0280 test_file << "Mock<ProjectManager> pmMock;" << std::endl; 0281 test_file << "When(Method(pmMock, undoStack)).AlwaysReturn(undoStack);" << std::endl; 0282 test_file << "ProjectManager &mocked = pmMock.get();" << std::endl; 0283 test_file << "pCore->m_projectManager = &mocked;" << std::endl; 0284 0285 size_t nbrConstructedTimelines = 0; 0286 auto check_consistancy = [&]() { 0287 for (size_t i = 0; i < nbrConstructedTimelines; ++i) { 0288 test_file << "REQUIRE(timeline_" << i << "->checkConsistency());" << std::endl; 0289 } 0290 }; 0291 for (const auto &o : operations) { 0292 bool isUndo = false; 0293 if (o.can_convert<Logger::Undo>()) { 0294 isUndo = true; 0295 Undo undo = o.convert<Logger::Undo>(); 0296 if (undo.undo) { 0297 test_file << "undoStack->undo();" << std::endl; 0298 fuzz_file << "u" << std::endl; 0299 } else { 0300 test_file << "undoStack->redo();" << std::endl; 0301 fuzz_file << "r" << std::endl; 0302 } 0303 } else if (o.can_convert<Logger::InvokId>()) { 0304 InvokId id = o.convert<Logger::InvokId>(); 0305 Invok &invok = invoks[id.id]; 0306 std::unordered_set<size_t> refs; 0307 bool is_static = false; 0308 rttr::method m = invok.ptr.get_type().get_method(invok.method); 0309 if (!m.is_valid()) { 0310 is_static = true; 0311 m = rttr::type::get_by_name("TimelineFunctions").get_method(invok.method); 0312 } 0313 if (!m.is_valid()) { 0314 std::cout << "ERROR: unknown method " << invok.method << std::endl; 0315 continue; 0316 } 0317 test_file << "{" << std::endl; 0318 for (const auto &a : m.get_parameter_infos()) { 0319 if (isIthParamARef(m, a.get_index())) { 0320 refs.insert(a.get_index()); 0321 test_file << a.get_type().get_name().to_string() << " dummy_" << std::to_string(a.get_index()) << ";" << std::endl; 0322 } 0323 } 0324 0325 if (m.get_return_type() != rttr::type::get<void>()) { 0326 test_file << m.get_return_type().get_name().to_string() << " res = "; 0327 } 0328 if (is_static) { 0329 test_file << "TimelineFunctions::" << invok.method << "(" << get_ptr_name(invok.ptr) << ", " << process_args(invok.args, refs) << ");" 0330 << std::endl; 0331 } else { 0332 test_file << get_ptr_name(invok.ptr) << "->" << invok.method << "(" << process_args(invok.args, refs) << ");" << std::endl; 0333 } 0334 if (m.get_return_type() != rttr::type::get<void>() && invok.res.is_valid()) { 0335 test_file << "REQUIRE( res == " << invok.res.to_string() << ");" << std::endl; 0336 } 0337 test_file << "}" << std::endl; 0338 0339 std::string invok_name = invok.method; 0340 if (translation_table.count(invok_name) > 0) { 0341 auto args = invok.args; 0342 if (rttr::type::get<TimelineModel>().get_method(invok_name).is_valid() || 0343 rttr::type::get<TimelineFunctions>().get_method(invok_name).is_valid()) { 0344 args.insert(args.begin(), invok.ptr); 0345 // adding an arg just messed up the references 0346 std::unordered_set<size_t> new_refs; 0347 for (const size_t &r : refs) { 0348 new_refs.insert(r + 1); 0349 } 0350 std::swap(refs, new_refs); 0351 } 0352 fuzz_file << translation_table[invok_name] << " " << process_args_fuzz(args, refs) << std::endl; 0353 } else { 0354 std::cout << "ERROR: unknown method " << invok_name << std::endl; 0355 } 0356 0357 } else if (o.can_convert<Logger::ConstrId>()) { 0358 ConstrId id = o.convert<Logger::ConstrId>(); 0359 std::string constr_name = std::string("constr_") + id.type; 0360 if (translation_table.count(constr_name) > 0) { 0361 fuzz_file << translation_table[constr_name] << " " << process_args_fuzz(constr[id.type][id.id].second) << std::endl; 0362 } else { 0363 std::cout << "ERROR: unknown constructor " << constr_name << std::endl; 0364 } 0365 if (id.type == "TimelineModel") { 0366 test_file << "TimelineItemModel tim_" << id.id << "(®_profile, undoStack);" << std::endl; 0367 test_file << "Mock<TimelineItemModel> timMock_" << id.id << "(tim_" << id.id << ");" << std::endl; 0368 test_file << "auto timeline_" << id.id << " = std::shared_ptr<TimelineItemModel>(&timMock_" << id.id << ".get(), [](...) {});" << std::endl; 0369 test_file << "TimelineItemModel::finishConstruct(timeline_" << id.id << ", guideModel);" << std::endl; 0370 test_file << "Fake(Method(timMock_" << id.id << ", adjustAssetRange));" << std::endl; 0371 nbrConstructedTimelines++; 0372 } else if (id.type == "TrackModel") { 0373 std::string params = process_args(constr[id.type][id.id].second); 0374 test_file << "TrackModel::construct(" << params << ");" << std::endl; 0375 } else if (id.type == "ClipModel") { 0376 std::string params = process_args(constr[id.type][id.id].second); 0377 test_file << "ClipModel::construct(" << params << ");" << std::endl; 0378 } else if (id.type == "test_producer") { 0379 std::string params = process_args(constr[id.type][id.id].second); 0380 test_file << "createProducer(reg_profile, " << params << ");" << std::endl; 0381 } else if (id.type == "test_producer_sound") { 0382 std::string params = process_args(constr[id.type][id.id].second); 0383 test_file << "createProducerWithSound(reg_profile, " << params << ");" << std::endl; 0384 } else { 0385 std::cout << "Error: unknown constructor " << id.type << std::endl; 0386 } 0387 } else { 0388 std::cout << "Error: unknown operation" << std::endl; 0389 } 0390 check_consistancy(); 0391 if (!isUndo) { 0392 test_file << "undoStack->undo();" << std::endl; 0393 check_consistancy(); 0394 test_file << "undoStack->redo();" << std::endl; 0395 check_consistancy(); 0396 } 0397 } 0398 test_file << "}" << std::endl; 0399 test_file << "pCore->m_projectManager = nullptr;" << std::endl; 0400 test_file << "}" << std::endl; 0401 } 0402 void Logger::clear() 0403 { 0404 is_executing = false; 0405 invoks.clear(); 0406 operations.clear(); 0407 constr.clear(); 0408 } 0409 0410 LogGuard::LogGuard() 0411 { 0412 m_hasGuard = Logger::start_logging(); 0413 } 0414 LogGuard::~LogGuard() 0415 { 0416 if (m_hasGuard) { 0417 Logger::stop_logging(); 0418 } 0419 } 0420 bool LogGuard::hasGuard() const 0421 { 0422 return m_hasGuard; 0423 } 0424 0425 void Logger::log_undo(bool undo) 0426 { 0427 Logger::Undo u; 0428 u.undo = undo; 0429 operations.push_back(u); 0430 }