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 << "(&reg_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 }