diff --git a/fuzzer/fuzzing.cpp b/fuzzer/fuzzing.cpp index e9b48ea68..3d837292a 100644 --- a/fuzzer/fuzzing.cpp +++ b/fuzzer/fuzzing.cpp @@ -1,493 +1,499 @@ /*************************************************************************** * Copyright (C) 2019 by Nicolas Carion * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "fuzzing.hpp" #include "bin/model/markerlistmodel.hpp" #include "doc/docundostack.hpp" #include "fakeit_standalone.hpp" #include "logger.hpp" #include #include #include #include #include #define private public #define protected public #include "assets/keyframes/model/keyframemodel.hpp" #include "assets/model/assetparametermodel.hpp" #include "bin/clipcreator.hpp" #include "bin/projectclip.h" #include "bin/projectfolder.h" #include "bin/projectitemmodel.h" #include "core.h" #include "effects/effectsrepository.hpp" #include "effects/effectstack/model/effectitemmodel.hpp" #include "effects/effectstack/model/effectstackmodel.hpp" #include "mltconnection.h" #include "project/projectmanager.h" #include "timeline2/model/clipmodel.hpp" #include "timeline2/model/compositionmodel.hpp" #include "timeline2/model/groupsmodel.hpp" #include "timeline2/model/timelinefunctions.hpp" #include "timeline2/model/timelineitemmodel.hpp" #include "timeline2/model/timelinemodel.hpp" #include "timeline2/model/trackmodel.hpp" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wsign-conversion" #pragma GCC diagnostic ignored "-Wfloat-equal" #pragma GCC diagnostic ignored "-Wshadow" #pragma GCC diagnostic ignored "-Wpedantic" #include #pragma GCC diagnostic pop using namespace fakeit; namespace { QString createProducer(Mlt::Profile &prof, std::string color, std::shared_ptr binModel, int length, bool limited) { Logger::log_create_producer("test_producer", {color, binModel, length, limited}); std::shared_ptr producer = std::make_shared(prof, "color", color.c_str()); producer->set("length", length); producer->set("out", length - 1); Q_ASSERT(producer->is_valid()); QString binId = QString::number(binModel->getFreeClipId()); auto binClip = ProjectClip::construct(binId, QIcon(), binModel, producer); if (limited) { binClip->forceLimitedDuration(); } Fun undo = []() { return true; }; Fun redo = []() { return true; }; Q_ASSERT(binModel->addItem(binClip, binModel->getRootFolder()->clipId(), undo, redo)); return binId; } QString createProducerWithSound(Mlt::Profile &prof, std::shared_ptr binModel) { Logger::log_create_producer("test_producer_sound", {binModel}); // std::shared_ptr producer = std::make_shared(prof, // QFileInfo("../tests/small.mkv").absoluteFilePath().toStdString().c_str()); // In case the test system does not have avformat support, we can switch to the integrated blipflash producer std::shared_ptr producer = std::make_shared(prof, "blipflash"); producer->set_in_and_out(0, 1); producer->set("kdenlive:duration", 2); Q_ASSERT(producer->is_valid()); QString binId = QString::number(binModel->getFreeClipId()); auto binClip = ProjectClip::construct(binId, QIcon(), binModel, producer); Fun undo = []() { return true; }; Fun redo = []() { return true; }; Q_ASSERT(binModel->addItem(binClip, binModel->getRootFolder()->clipId(), undo, redo)); return binId; } inline int modulo(int a, int b) { const int result = a % b; return result >= 0 ? result : result + b; } namespace { bool isIthParamARef(const rttr::method &method, size_t i) { QString sig = QString::fromStdString(method.get_signature().to_string()); int deb = sig.indexOf("("); int end = sig.lastIndexOf(")"); sig = sig.mid(deb + 1, deb - end - 1); QStringList args = sig.split(QStringLiteral(",")); return args[(int)i].contains("&") && !args[(int)i].contains("const &"); } } // namespace } // namespace void fuzz(const std::string &input) { Logger::init(); Logger::clear(); std::stringstream ss; ss << input; Mlt::Profile profile; auto binModel = pCore->projectItemModel(); binModel->clean(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); TimelineModel::next_id = 0; Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; std::vector> all_timelines; std::unordered_map, std::vector> all_clips, all_tracks, all_compositions, all_groups; auto update_elems = [&]() { all_clips.clear(); all_tracks.clear(); all_compositions.clear(); for (const auto &timeline : all_timelines) { all_clips[timeline] = {}; all_tracks[timeline] = {}; all_compositions[timeline] = {}; all_groups[timeline] = {}; auto &clips = all_clips[timeline]; clips.clear(); for (const auto &c : timeline->m_allClips) { clips.push_back(c.first); } std::sort(clips.begin(), clips.end()); auto &compositions = all_compositions[timeline]; compositions.clear(); for (const auto &c : timeline->m_allCompositions) { compositions.push_back(c.first); } std::sort(compositions.begin(), compositions.end()); auto &tracks = all_tracks[timeline]; tracks.clear(); for (const auto &c : timeline->m_iteratorTable) { tracks.push_back(c.first); } std::sort(tracks.begin(), tracks.end()); auto &groups = all_groups[timeline]; groups.clear(); for (int c : timeline->m_allGroups) { groups.push_back(c); } std::sort(groups.begin(), groups.end()); } }; auto get_timeline = [&]() -> std::shared_ptr { int id = 0; ss >> id; if (all_timelines.size() == 0) return nullptr; id = modulo(id, (int)all_timelines.size()); return all_timelines[size_t(id)]; }; auto get_group = [&](std::shared_ptr timeline) { int id = 0; ss >> id; if (!timeline) return -1; if (timeline->isGroup(id)) return id; if (all_timelines.size() == 0) return -1; if (all_groups.count(timeline) == 0) return -1; if (all_groups[timeline].size() == 0) return -1; id = modulo(id, (int)all_groups[timeline].size()); return all_groups[timeline][id]; }; auto get_clip = [&](std::shared_ptr timeline) { int id = 0; ss >> id; if (!timeline) return -1; if (timeline->isClip(id)) return id; if (all_timelines.size() == 0) return -1; if (all_clips.count(timeline) == 0) return -1; if (all_clips[timeline].size() == 0) return -1; id = modulo(id, (int)all_clips[timeline].size()); return all_clips[timeline][id]; }; auto get_compo = [&](std::shared_ptr timeline) { int id = 0; ss >> id; if (!timeline) return -1; if (timeline->isComposition(id)) return id; if (all_timelines.size() == 0) return -1; if (all_compositions.count(timeline) == 0) return -1; if (all_compositions[timeline].size() == 0) return -1; id = modulo(id, (int)all_compositions[timeline].size()); return all_compositions[timeline][id]; }; auto get_item = [&](std::shared_ptr timeline) { int id = 0; ss >> id; if (!timeline) return -1; if (timeline->isClip(id)) return id; if (timeline->isComposition(id)) return id; if (all_timelines.size() == 0) return -1; int clip_count = 0; if (all_clips.count(timeline) > 0) { clip_count = all_clips[timeline].size(); } int compo_count = 0; if (all_compositions.count(timeline) > 0) { compo_count = all_compositions[timeline].size(); } if (clip_count + compo_count == 0) return -1; id = modulo(id, clip_count + compo_count); if (id < clip_count) { return all_clips[timeline][id]; } return all_compositions[timeline][id - clip_count]; }; auto get_track = [&](std::shared_ptr timeline) { int id = 0; ss >> id; if (!timeline) return -1; if (timeline->isTrack(id)) return id; if (all_timelines.size() == 0) return -1; if (all_tracks.count(timeline) == 0) return -1; if (all_tracks[timeline].size() == 0) return -1; id = modulo(id, (int)all_tracks[timeline].size()); return all_tracks[timeline][id]; }; std::string c; while (ss >> c) { - if (Logger::back_translation_table.count(c) > 0) { - std::cout << "found=" << c; + if (c == "u") { + std::cout << "UNDOING" << std::endl; + undoStack->undo(); + } else if (c == "r") { + std::cout << "REDOING" << std::endl; + undoStack->redo(); + } else if (Logger::back_translation_table.count(c) > 0) { + // std::cout << "found=" << c; c = Logger::back_translation_table[c]; // std::cout << " tranlated=" << c << std::endl; if (c == "constr_TimelineModel") { all_timelines.emplace_back(TimelineItemModel::construct(&profile, guideModel, undoStack)); } else if (c == "constr_ClipModel") { auto timeline = get_timeline(); int id = 0, state_id; double speed = 1; PlaylistState::ClipState state = PlaylistState::VideoOnly; std::string binId; ss >> binId >> id >> state_id >> speed; state = static_cast(state_id); if (timeline) { ClipModel::construct(timeline, QString::fromStdString(binId), -1, state, speed); } } else if (c == "constr_TrackModel") { auto timeline = get_timeline(); int id, pos = 0; std::string name; bool audio = false; ss >> id >> pos >> name >> audio; if (name == "$$") { name = ""; } if (pos < -1) pos = 0; pos = std::min((int)all_tracks[timeline].size(), pos); if (timeline) { TrackModel::construct(timeline, -1, pos, QString::fromStdString(name), audio); } } else if (c == "constr_test_producer") { std::string color; int length = 0; bool limited = false; ss >> color >> length >> limited; createProducer(profile, color, binModel, length, limited); } else if (c == "constr_test_producer_sound") { createProducerWithSound(profile, binModel); } else { - std::cout << "executing " << c << std::endl; + // std::cout << "executing " << c << std::endl; rttr::type target_type = rttr::type::get(); bool found = false; for (const std::string &t : {"TimelineModel", "TimelineFunctions"}) { rttr::type current_type = rttr::type::get_by_name(t); - std::cout << "type " << t << " has methods count=" << current_type.get_methods().size() << std::endl; + // std::cout << "type " << t << " has methods count=" << current_type.get_methods().size() << std::endl; if (current_type.get_method(c).is_valid()) { found = true; target_type = current_type; break; } } if (found) { - std::cout << "found!" << std::endl; + // std::cout << "found!" << std::endl; bool valid = true; rttr::method target_method = target_type.get_method(c); std::vector arguments; rttr::variant ptr; if (target_type == rttr::type::get()) { if (all_timelines.size() == 0) { valid = false; } ptr = get_timeline(); } int i = -1; for (const auto &p : target_method.get_parameter_infos()) { ++i; std::string arg_name = p.get_name().to_string(); - std::cout << arg_name << std::endl; + // std::cout << arg_name << std::endl; if (arg_name == "compoId") { std::shared_ptr tim = (ptr.can_convert>() ? ptr.convert>() : nullptr); int compoId = get_compo(tim); valid = valid && (compoId >= 0); // std::cout << "got compo" << compoId << std::endl; arguments.emplace_back(compoId); } else if (arg_name == "clipId") { std::shared_ptr tim = (ptr.can_convert>() ? ptr.convert>() : nullptr); int clipId = get_clip(tim); valid = valid && (clipId >= 0); arguments.emplace_back(clipId); - std::cout << "got clipId" << clipId << std::endl; + // std::cout << "got clipId" << clipId << std::endl; } else if (arg_name == "trackId") { std::shared_ptr tim = (ptr.can_convert>() ? ptr.convert>() : nullptr); int trackId = get_track(tim); valid = valid && (trackId >= 0); arguments.emplace_back(trackId); // std::cout << "got trackId" << trackId << std::endl; } else if (arg_name == "itemId") { std::shared_ptr tim = (ptr.can_convert>() ? ptr.convert>() : nullptr); int itemId = get_item(tim); valid = valid && (itemId >= 0); arguments.emplace_back(itemId); // std::cout << "got itemId" << itemId << std::endl; } else if (arg_name == "groupId") { std::shared_ptr tim = (ptr.can_convert>() ? ptr.convert>() : nullptr); int groupId = get_group(tim); valid = valid && (groupId >= 0); arguments.emplace_back(groupId); // std::cout << "got clipId" << clipId << std::endl; } else if (arg_name == "logUndo") { bool a = false; ss >> a; // we enforce undo logging a = true; arguments.emplace_back(a); } else if (arg_name == "itemIds") { int count = 0; ss >> count; // std::cout << "got ids. going to read count=" << count << std::endl; if (count > 0) { std::shared_ptr tim = (ptr.can_convert>() ? ptr.convert>() : nullptr); std::unordered_set ids; for (int i = 0; i < count; ++i) { int itemId = get_item(tim); // std::cout << "\t read" << itemId << std::endl; valid = valid && (itemId >= 0); ids.insert(itemId); } arguments.emplace_back(ids); } else { valid = false; } } else if (!isIthParamARef(target_method, i)) { rttr::type arg_type = p.get_type(); if (arg_type == rttr::type::get()) { int a = 0; ss >> a; - std::cout << "read int " << a << std::endl; + // std::cout << "read int " << a << std::endl; arguments.emplace_back(a); } else if (arg_type == rttr::type::get()) { size_t a = 0; ss >> a; arguments.emplace_back(a); } else if (arg_type == rttr::type::get()) { double a = 0; ss >> a; arguments.emplace_back(a); } else if (arg_type == rttr::type::get()) { float a = 0; ss >> a; arguments.emplace_back(a); } else if (arg_type == rttr::type::get()) { bool a = false; ss >> a; // std::cout << "read bool " << a << std::endl; arguments.emplace_back(a); } else if (arg_type == rttr::type::get()) { std::string str = ""; ss >> str; // std::cout << "read str " << str << std::endl; if (str == "$$") { str = ""; } arguments.emplace_back(QString::fromStdString(str)); } else if (arg_type == rttr::type::get>()) { auto timeline = get_timeline(); if (timeline) { - std::cout << "got timeline" << std::endl; + // std::cout << "got timeline" << std::endl; auto timeline2 = std::dynamic_pointer_cast(timeline); arguments.emplace_back(timeline2); ptr = timeline; } else { - std::cout << "didn't get timeline" << std::endl; + // std::cout << "didn't get timeline" << std::endl; valid = false; } } else if (arg_type.is_enumeration()) { int a = 0; ss >> a; rttr::variant var_a = a; var_a.convert((const rttr::type &)arg_type); // std::cout << "read enum " << arg_type.get_enumeration().value_to_name(var_a).to_string() << std::endl; arguments.push_back(var_a); } else { std::cout << "ERROR: unsupported arg type " << arg_type.get_name().to_string() << std::endl; assert(false); } } else { if (p.get_type() == rttr::type::get()) { arguments.emplace_back(-1); } else { assert(false); } } } if (valid) { std::cout << "VALID!!! " << target_method.get_name().to_string() << std::endl; std::vector args; args.reserve(arguments.size()); for (auto &a : arguments) { args.emplace_back(a); - std::cout << "argument=" << a.get_type().get_name().to_string() << std::endl; + // std::cout << "argument=" << a.get_type().get_name().to_string() << std::endl; } for (const auto &p : target_method.get_parameter_infos()) { - std::cout << "expected=" << p.get_type().get_name().to_string() << std::endl; + // std::cout << "expected=" << p.get_type().get_name().to_string() << std::endl; } rttr::variant res = target_method.invoke_variadic(ptr, args); if (res.is_valid()) { std::cout << "SUCCESS!!!" << std::endl; } else { std::cout << "!!!FAILLLLLL!!!" << std::endl; } } } } update_elems(); for (const auto &t : all_timelines) { assert(t->checkConsistency()); } } } all_clips.clear(); all_tracks.clear(); all_compositions.clear(); all_groups.clear(); for (auto &all_timeline : all_timelines) { all_timeline.reset(); } pCore->m_projectManager = nullptr; Core::m_self.reset(); MltConnection::m_self.reset(); std::cout << "---------------------------------------------------------------------------------------------------------------------------------------------" "---------------" << std::endl; } diff --git a/src/logger.cpp b/src/logger.cpp index f34ef9c3a..32fefc402 100644 --- a/src/logger.cpp +++ b/src/logger.cpp @@ -1,422 +1,446 @@ /*************************************************************************** * Copyright (C) 2019 by Nicolas Carion * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "logger.hpp" #include "bin/projectitemmodel.h" #include "timeline2/model/timelinefunctions.hpp" #include "timeline2/model/timelineitemmodel.hpp" #include "timeline2/model/timelinemodel.hpp" #include #include #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wsign-conversion" #pragma GCC diagnostic ignored "-Wfloat-equal" #pragma GCC diagnostic ignored "-Wshadow" #pragma GCC diagnostic ignored "-Wpedantic" #include #pragma GCC diagnostic pop thread_local bool Logger::is_executing = false; std::mutex Logger::mut; std::vector Logger::operations; std::vector Logger::invoks; std::unordered_map> Logger::constr; std::unordered_map Logger::translation_table; std::unordered_map Logger::back_translation_table; int Logger::dump_count = 0; thread_local size_t Logger::result_awaiting = INT_MAX; void Logger::init() { std::string cur_ind = "a"; auto incr_ind = [&](auto &&self, size_t i = 0) { if (i >= cur_ind.size()) { cur_ind += "a"; return; } if (cur_ind[i] == 'z') { cur_ind[i] = 'A'; } else if (cur_ind[i] == 'Z') { cur_ind[i] = 'a'; self(self, i + 1); } else { cur_ind[i]++; } + if (cur_ind == "u" || cur_ind == "r") { + // reserved for undo and redo + self(self, i); + } }; for (const auto &o : {"TimelineModel", "TrackModel", "test_producer", "test_producer_sound", "ClipModel"}) { translation_table[std::string("constr_") + o] = cur_ind; incr_ind(incr_ind); } for (const auto &m : rttr::type::get().get_methods()) { translation_table[m.get_name().to_string()] = cur_ind; incr_ind(incr_ind); } for (const auto &m : rttr::type::get().get_methods()) { translation_table[m.get_name().to_string()] = cur_ind; incr_ind(incr_ind); } for (const auto &i : translation_table) { back_translation_table[i.second] = i.first; } } bool Logger::start_logging() { std::unique_lock lk(mut); if (is_executing) { return false; } is_executing = true; return true; } void Logger::stop_logging() { std::unique_lock lk(mut); is_executing = false; } std::string Logger::get_ptr_name(const rttr::variant &ptr) { if (ptr.can_convert()) { return "timeline_" + std::to_string(get_id_from_ptr(ptr.convert())); } else if (ptr.can_convert()) { return "binModel"; } else if (ptr.can_convert()) { return "timeline_" + std::to_string(get_id_from_ptr(static_cast(ptr.convert()))); } else { std::cout << "Error: unhandled ptr type " << ptr.get_type().get_name().to_string() << std::endl; } return "unknown"; } void Logger::log_res(rttr::variant result) { std::unique_lock lk(mut); Q_ASSERT(result_awaiting < invoks.size()); invoks[result_awaiting].res = std::move(result); } void Logger::log_create_producer(const std::string &type, std::vector args) { std::unique_lock lk(mut); for (auto &a : args) { // this will rewove shared/weak/unique ptrs if (a.get_type().is_wrapper()) { a = a.extract_wrapped_value(); } const std::string class_name = a.get_type().get_name().to_string(); } constr[type].push_back({type, std::move(args)}); operations.emplace_back(ConstrId{type, constr[type].size() - 1}); } namespace { bool isIthParamARef(const rttr::method &method, size_t i) { QString sig = QString::fromStdString(method.get_signature().to_string()); int deb = sig.indexOf("("); int end = sig.lastIndexOf(")"); sig = sig.mid(deb + 1, deb - end - 1); QStringList args = sig.split(QStringLiteral(",")); return args[(int)i].contains("&") && !args[(int)i].contains("const &"); } std::string quoted(const std::string &input) { #if __cpp_lib_quoted_string_io std::stringstream ss; ss << std::quoted(input); return ss.str(); #else // very incomplete implem return "\"" + input + "\""; #endif } } // namespace void Logger::print_trace() { dump_count++; auto process_args = [&](const std::vector &args, const std::unordered_set &refs = {}) { std::stringstream ss; bool deb = true; size_t i = 0; for (const auto &a : args) { if (deb) { deb = false; i = 0; } else { ss << ", "; ++i; } if (refs.count(i) > 0) { ss << "dummy_" << i; } else if (a.get_type() == rttr::type::get()) { ss << a.convert(); } else if (a.get_type() == rttr::type::get()) { ss << a.convert(); } else if (a.get_type() == rttr::type::get()) { ss << a.convert(); } else if (a.get_type() == rttr::type::get()) { ss << a.convert(); } else if (a.get_type() == rttr::type::get()) { ss << (a.convert() ? "true" : "false"); } else if (a.get_type().is_enumeration()) { auto e = a.get_type().get_enumeration(); ss << e.get_name().to_string() << "::" << a.convert(); } else if (a.can_convert()) { ss << quoted(a.convert().toStdString()); } else if (a.can_convert()) { ss << quoted(a.convert()); } else if (a.can_convert>()) { auto set = a.convert>(); ss << "{"; bool beg = true; for (int s : set) { if (beg) beg = false; else ss << ", "; ss << s; } ss << "}"; } else if (a.get_type().is_pointer()) { ss << get_ptr_name(a); } else { std::cout << "Error: unhandled arg type " << a.get_type().get_name().to_string() << std::endl; } } return ss.str(); }; auto process_args_fuzz = [&](const std::vector &args, const std::unordered_set &refs = {}) { std::stringstream ss; bool deb = true; size_t i = 0; for (const auto &a : args) { if (deb) { deb = false; i = 0; } else { ss << " "; ++i; } if (refs.count(i) > 0) { continue; } else if (a.get_type() == rttr::type::get()) { ss << a.convert(); } else if (a.get_type() == rttr::type::get()) { ss << a.convert(); } else if (a.get_type() == rttr::type::get()) { ss << a.convert(); } else if (a.get_type() == rttr::type::get()) { ss << a.convert(); } else if (a.get_type() == rttr::type::get()) { ss << (a.convert() ? "1" : "0"); } else if (a.get_type().is_enumeration()) { ss << a.convert(); } else if (a.can_convert()) { std::string out = a.convert().toStdString(); if (out.empty()) { out = "$$"; } ss << out; } else if (a.can_convert()) { std::string out = a.convert(); if (out.empty()) { out = "$$"; } ss << out; } else if (a.can_convert>()) { auto set = a.convert>(); ss << set.size() << " "; bool beg = true; for (int s : set) { if (beg) beg = false; else ss << " "; ss << s; } } else if (a.get_type().is_pointer()) { if (a.can_convert()) { ss << get_id_from_ptr(a.convert()); } else if (a.can_convert()) { ss << get_id_from_ptr(static_cast(a.convert())); } else if (a.can_convert()) { // only one binModel, we skip the parameter since it's unambiguous } else { std::cout << "Error: unhandled ptr type " << a.get_type().get_name().to_string() << std::endl; } } else { std::cout << "Error: unhandled arg type " << a.get_type().get_name().to_string() << std::endl; } } return ss.str(); }; std::ofstream fuzz_file; fuzz_file.open("fuzz_case_" + std::to_string(dump_count) + ".txt"); std::ofstream test_file; test_file.open("test_case_" + std::to_string(dump_count) + ".cpp"); test_file << "TEST_CASE(\"Regression\") {" << std::endl; test_file << "auto binModel = pCore->projectItemModel();" << std::endl; test_file << "binModel->clean();" << std::endl; test_file << "std::shared_ptr undoStack = std::make_shared(nullptr);" << std::endl; test_file << "std::shared_ptr guideModel = std::make_shared(undoStack);" << std::endl; test_file << "TimelineModel::next_id = 0;" << std::endl; test_file << "{" << std::endl; test_file << "Mock pmMock;" << std::endl; test_file << "When(Method(pmMock, undoStack)).AlwaysReturn(undoStack);" << std::endl; test_file << "ProjectManager &mocked = pmMock.get();" << std::endl; test_file << "pCore->m_projectManager = &mocked;" << std::endl; size_t nbrConstructedTimelines = 0; auto check_consistancy = [&]() { for (size_t i = 0; i < nbrConstructedTimelines; ++i) { test_file << "REQUIRE(timeline_" << i << "->checkConsistency());" << std::endl; } }; for (const auto &o : operations) { - if (o.can_convert()) { + bool isUndo = false; + if (o.can_convert()) { + isUndo = true; + Undo undo = o.convert(); + if (undo.undo) { + test_file << "undoStack->undo();" << std::endl; + fuzz_file << "u" << std::endl; + } else { + test_file << "undoStack->redo();" << std::endl; + fuzz_file << "r" << std::endl; + } + } else if (o.can_convert()) { InvokId id = o.convert(); Invok &invok = invoks[id.id]; std::unordered_set refs; bool is_static = false; rttr::method m = invok.ptr.get_type().get_method(invok.method); if (!m.is_valid()) { is_static = true; m = rttr::type::get_by_name("TimelineFunctions").get_method(invok.method); } if (!m.is_valid()) { std::cout << "ERROR: unknown method " << invok.method << std::endl; continue; } test_file << "{" << std::endl; for (const auto &a : m.get_parameter_infos()) { if (isIthParamARef(m, a.get_index())) { refs.insert(a.get_index()); test_file << a.get_type().get_name().to_string() << " dummy_" << std::to_string(a.get_index()) << ";" << std::endl; } } if (m.get_return_type() != rttr::type::get()) { test_file << m.get_return_type().get_name().to_string() << " res = "; } if (is_static) { test_file << "TimelineFunctions::" << invok.method << "(" << get_ptr_name(invok.ptr) << ", " << process_args(invok.args, refs) << ");" << std::endl; } else { test_file << get_ptr_name(invok.ptr) << "->" << invok.method << "(" << process_args(invok.args, refs) << ");" << std::endl; } if (m.get_return_type() != rttr::type::get() && invok.res.is_valid()) { test_file << "REQUIRE( res == " << invok.res.to_string() << ");" << std::endl; } test_file << "}" << std::endl; std::string invok_name = invok.method; if (translation_table.count(invok_name) > 0) { auto args = invok.args; if (rttr::type::get().get_method(invok_name).is_valid() || rttr::type::get().get_method(invok_name).is_valid()) { args.insert(args.begin(), invok.ptr); // adding an arg just messed up the references std::unordered_set new_refs; for (const size_t &r : refs) { new_refs.insert(r + 1); } std::swap(refs, new_refs); } fuzz_file << translation_table[invok_name] << " " << process_args_fuzz(args, refs) << std::endl; } else { std::cout << "ERROR: unknown method " << invok_name << std::endl; } } else if (o.can_convert()) { ConstrId id = o.convert(); std::string constr_name = std::string("constr_") + id.type; if (translation_table.count(constr_name) > 0) { fuzz_file << translation_table[constr_name] << " " << process_args_fuzz(constr[id.type][id.id].second) << std::endl; } else { std::cout << "ERROR: unknown constructor " << constr_name << std::endl; } if (id.type == "TimelineModel") { test_file << "TimelineItemModel tim_" << id.id << "(®_profile, undoStack);" << std::endl; test_file << "Mock timMock_" << id.id << "(tim_" << id.id << ");" << std::endl; test_file << "auto timeline_" << id.id << " = std::shared_ptr(&timMock_" << id.id << ".get(), [](...) {});" << std::endl; test_file << "TimelineItemModel::finishConstruct(timeline_" << id.id << ", guideModel);" << std::endl; test_file << "Fake(Method(timMock_" << id.id << ", adjustAssetRange));" << std::endl; nbrConstructedTimelines++; } else if (id.type == "TrackModel") { std::string params = process_args(constr[id.type][id.id].second); test_file << "TrackModel::construct(" << params << ");" << std::endl; } else if (id.type == "ClipModel") { std::string params = process_args(constr[id.type][id.id].second); test_file << "ClipModel::construct(" << params << ");" << std::endl; } else if (id.type == "test_producer") { std::string params = process_args(constr[id.type][id.id].second); test_file << "createProducer(reg_profile, " << params << ");" << std::endl; } else if (id.type == "test_producer_sound") { std::string params = process_args(constr[id.type][id.id].second); test_file << "createProducerWithSound(reg_profile, " << params << ");" << std::endl; } else { std::cout << "Error: unknown constructor " << id.type << std::endl; } } else { std::cout << "Error: unknown operation" << std::endl; } check_consistancy(); - test_file << "undoStack->undo();" << std::endl; - check_consistancy(); - test_file << "undoStack->redo();" << std::endl; - check_consistancy(); + if (!isUndo) { + test_file << "undoStack->undo();" << std::endl; + check_consistancy(); + test_file << "undoStack->redo();" << std::endl; + check_consistancy(); + } } test_file << "}" << std::endl; test_file << "pCore->m_projectManager = nullptr;" << std::endl; test_file << "}" << std::endl; } void Logger::clear() { is_executing = false; invoks.clear(); operations.clear(); constr.clear(); } LogGuard::LogGuard() { m_hasGuard = Logger::start_logging(); } LogGuard::~LogGuard() { if (m_hasGuard) { Logger::stop_logging(); } } bool LogGuard::hasGuard() const { return m_hasGuard; } + +void Logger::log_undo(bool undo) +{ + Logger::Undo u; + u.undo = undo; + operations.push_back(u); +} diff --git a/src/logger.hpp b/src/logger.hpp index db0b47bfb..21af41557 100644 --- a/src/logger.hpp +++ b/src/logger.hpp @@ -1,187 +1,194 @@ /*************************************************************************** * Copyright (C) 2019 by Nicolas Carion * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that stdd::it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #pragma once #include #include #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wsign-conversion" #pragma GCC diagnostic ignored "-Wfloat-equal" #pragma GCC diagnostic ignored "-Wshadow" #pragma GCC diagnostic ignored "-Wpedantic" #include #pragma GCC diagnostic pop /** @brief This class is meant to provide an easy way to reproduce bugs involving the model. * The idea is to log any modifier function involving a model class, and trace the parameters that were passed, to be able to generate a test-case producing the * same behaviour. Note that many modifier functions of the models are nested. We are only interested in the top-most call, and we must ignore bottom calls. */ class Logger { public: /// @brief Inits the logger. Must be called at startup static void init(); /** @brief Notify the logger that the current thread wants to start logging. * This function returns true if this is a top-level call, meaning that we indeed want to log it. If the function returns false, the caller must not log. */ static bool start_logging(); /** @brief This logs the construction of an object of type T, whose new instance is passed. The instance will be kept around in case future calls refer to * it. The arguments should more or less match the constructor arguments. In general, it's better to call the corresponding macro TRACE_CONSTR */ template static void log_constr(T *inst, std::vector args); /** @brief Logs the call to a member function on a given instance of class T. The string contains the method name, and then the vector contains all the * parameters. In general, the method should be registered in RTTR. It's better to call the corresponding macro TRACE() if appropriate */ template static void log(T *inst, std::string str, std::vector args); static void log_create_producer(const std::string &type, std::vector args); /** @brief When the last function logged has a return value, you can log it through this function, by passing the corresponding value. In general, it's * better to call the macro TRACE_RES */ static void log_res(rttr::variant result); + // log whenever an undo/redo occured + static void log_undo(bool undo); + /// @brief Notify that we are done with our function. Must not be called if start_logging returned false. static void stop_logging(); static void print_trace(); /// @brief Resets the current log static void clear(); static std::unordered_map translation_table; static std::unordered_map back_translation_table; protected: /** @brief Look amongst the known instances to get the name of a given pointer */ static std::string get_ptr_name(const rttr::variant &ptr); template static size_t get_id_from_ptr(T *ptr); struct InvokId { size_t id; }; struct ConstrId { std::string type; size_t id; }; + struct Undo + { + bool undo; + }; // a construction log contains the pointer as first parameter, and the vector of parameters using Constr = std::pair>; struct Invok { rttr::variant ptr; std::string method; std::vector args; rttr::variant res; }; thread_local static bool is_executing; thread_local static size_t result_awaiting; static std::mutex mut; static std::vector operations; static std::unordered_map> constr; static std::vector invoks; static int dump_count; }; /** @brief This class provides a RAII mechanism to log the execution of a function */ class LogGuard { public: LogGuard(); ~LogGuard(); // @brief Returns true if we are the top-level caller. bool hasGuard() const; protected: bool m_hasGuard = false; }; /// See Logger::log_constr. Note that the macro fills in the ptr instance for you. #define TRACE_CONSTR(ptr, ...) \ LogGuard __guard; \ if (__guard.hasGuard()) { \ Logger::log_constr((ptr), {__VA_ARGS__}); \ } /// See Logger::log. Note that the macro fills the ptr instance and the method name for you. #define TRACE(...) \ LogGuard __guard; \ if (__guard.hasGuard()) { \ Logger::log(this, __FUNCTION__, {__VA_ARGS__}); \ } /// Same as TRACE, but called from a static function #define TRACE_STATIC(ptr, ...) \ LogGuard __guard; \ if (__guard.hasGuard()) { \ Logger::log(ptr.get(), __FUNCTION__, {__VA_ARGS__}); \ } /// See Logger::log_res #define TRACE_RES(res) \ if (__guard.hasGuard()) { \ Logger::log_res(res); \ } /******* Implementations ***********/ template void Logger::log_constr(T *inst, std::vector args) { std::unique_lock lk(mut); for (auto &a : args) { // this will rewove shared/weak/unique ptrs if (a.get_type().is_wrapper()) { a = a.extract_wrapped_value(); } } std::string class_name = rttr::type::get().get_name().to_string(); constr[class_name].push_back({inst, std::move(args)}); operations.emplace_back(ConstrId{class_name, constr[class_name].size() - 1}); } template void Logger::log(T *inst, std::string fctName, std::vector args) { std::unique_lock lk(mut); for (auto &a : args) { // this will rewove shared/weak/unique ptrs if (a.get_type().is_wrapper()) { a = a.extract_wrapped_value(); } } std::string class_name = rttr::type::get().get_name().to_string(); invoks.push_back({inst, std::move(fctName), std::move(args), rttr::variant()}); operations.emplace_back(InvokId{invoks.size() - 1}); result_awaiting = invoks.size() - 1; } template size_t Logger::get_id_from_ptr(T *ptr) { const std::string class_name = rttr::type::get().get_name().to_string(); for (size_t i = 0; i < constr.at(class_name).size(); ++i) { if (constr.at(class_name)[i].first.convert() == ptr) { return i; } } std::cerr << "Error: ptr of type " << class_name << " not found" << std::endl; return INT_MAX; } diff --git a/src/undohelper.cpp b/src/undohelper.cpp index b537fa9d7..2da2dd63a 100644 --- a/src/undohelper.cpp +++ b/src/undohelper.cpp @@ -1,49 +1,52 @@ /*************************************************************************** * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "undohelper.hpp" +#include "logger.hpp" #include #include FunctionalUndoCommand::FunctionalUndoCommand(Fun undo, Fun redo, const QString &text, QUndoCommand *parent) : QUndoCommand(parent) , m_undo(std::move(undo)) , m_redo(std::move(redo)) , m_undone(false) { setText(text); } void FunctionalUndoCommand::undo() { // qDebug() << "UNDOING " <