diff --git a/tests/compositiontest.cpp b/tests/compositiontest.cpp index 72ccc1488..9d277ba89 100644 --- a/tests/compositiontest.cpp +++ b/tests/compositiontest.cpp @@ -1,430 +1,416 @@ -#include "catch.hpp" -#include "doc/docundostack.hpp" -#include -#include -#include - -#include -#include -#include -#include -#define private public -#define protected public -#include "bin/model/markerlistmodel.hpp" -#include "timeline2/model/clipmodel.hpp" -#include "timeline2/model/compositionmodel.hpp" -#include "timeline2/model/timelineitemmodel.hpp" -#include "timeline2/model/timelinemodel.hpp" -#include "timeline2/model/trackmodel.hpp" -#include "transitions/transitionsrepository.hpp" +#include "test_utils.hpp" Mlt::Profile profile_composition; QString aCompo; TEST_CASE("Basic creation/deletion of a composition", "[CompositionModel]") { + Logger::clear(); // Check whether repo works QVector> transitions = TransitionsRepository::get()->getNames(); REQUIRE(!transitions.isEmpty()); // Look for a compo for (const auto &trans : transitions) { if (TransitionsRepository::get()->isComposition(trans.first)) { aCompo = trans.first; break; } } REQUIRE(!aCompo.isEmpty()); // Check construction from repo std::unique_ptr mlt_transition(TransitionsRepository::get()->getTransition(aCompo)); REQUIRE(mlt_transition->is_valid()); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel(new MarkerListModel(undoStack)); std::shared_ptr timeline = TimelineItemModel::construct(&profile_composition, guideModel, undoStack); REQUIRE(timeline->getCompositionsCount() == 0); int id1 = CompositionModel::construct(timeline, aCompo); REQUIRE(timeline->getCompositionsCount() == 1); int id2 = CompositionModel::construct(timeline, aCompo); REQUIRE(timeline->getCompositionsCount() == 2); int id3 = CompositionModel::construct(timeline, aCompo); REQUIRE(timeline->getCompositionsCount() == 3); // Test deletion REQUIRE(timeline->requestItemDeletion(id2)); REQUIRE(timeline->getCompositionsCount() == 2); REQUIRE(timeline->requestItemDeletion(id3)); REQUIRE(timeline->getCompositionsCount() == 1); REQUIRE(timeline->requestItemDeletion(id1)); REQUIRE(timeline->getCompositionsCount() == 0); + Logger::print_trace(); } TEST_CASE("Composition manipulation", "[CompositionModel]") { + Logger::clear(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel(new MarkerListModel(undoStack)); std::shared_ptr timeline = TimelineItemModel::construct(&profile_composition, guideModel, undoStack); int tid0 = TrackModel::construct(timeline); Q_UNUSED(tid0); int tid1 = TrackModel::construct(timeline); int cid2 = CompositionModel::construct(timeline, aCompo); int tid2 = TrackModel::construct(timeline); int tid3 = TrackModel::construct(timeline); Q_UNUSED(tid3); int cid1 = CompositionModel::construct(timeline, aCompo); REQUIRE(timeline->getCompositionPlaytime(cid1) == 1); REQUIRE(timeline->getCompositionPlaytime(cid2) == 1); SECTION("Insert a composition in a track and change track") { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackCompositionsCount(tid1) == 0); REQUIRE(timeline->getTrackCompositionsCount(tid2) == 0); REQUIRE(timeline->getCompositionPlaytime(cid1) == 1); REQUIRE(timeline->getCompositionPlaytime(cid2) == 1); REQUIRE(timeline->getCompositionTrackId(cid1) == -1); REQUIRE(timeline->getCompositionPosition(cid1) == -1); int pos = 10; REQUIRE(timeline->requestCompositionMove(cid1, tid1, pos)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getCompositionTrackId(cid1) == tid1); REQUIRE(timeline->getCompositionPosition(cid1) == pos); REQUIRE(timeline->getCompositionPlaytime(cid1) == 1); REQUIRE(timeline->getTrackCompositionsCount(tid1) == 1); REQUIRE(timeline->getTrackCompositionsCount(tid2) == 0); pos = 10; REQUIRE(timeline->requestCompositionMove(cid1, tid2, pos)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getCompositionTrackId(cid1) == tid2); REQUIRE(timeline->getCompositionPosition(cid1) == pos); REQUIRE(timeline->getCompositionPlaytime(cid1) == 1); REQUIRE(timeline->getTrackCompositionsCount(tid2) == 1); REQUIRE(timeline->getTrackCompositionsCount(tid1) == 0); REQUIRE(timeline->requestItemResize(cid1, 10, true) > -1); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getCompositionTrackId(cid1) == tid2); REQUIRE(timeline->getCompositionPosition(cid1) == pos); REQUIRE(timeline->getCompositionPlaytime(cid1) == 10); REQUIRE(timeline->getTrackCompositionsCount(tid2) == 1); REQUIRE(timeline->getTrackCompositionsCount(tid1) == 0); REQUIRE(timeline->requestCompositionMove(cid2, tid2, 0)); REQUIRE(timeline->requestItemResize(cid2, 10, true) > -1); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getCompositionPlaytime(cid2) == 10); // Check conflicts int pos2 = timeline->getCompositionPlaytime(cid1); REQUIRE(timeline->requestCompositionMove(cid2, tid1, pos2)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getCompositionTrackId(cid2) == tid1); REQUIRE(timeline->getCompositionPosition(cid2) == pos2); REQUIRE(timeline->getTrackCompositionsCount(tid2) == 1); REQUIRE(timeline->getTrackCompositionsCount(tid1) == 1); REQUIRE_FALSE(timeline->requestCompositionMove(cid1, tid1, pos2 + 2)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackCompositionsCount(tid2) == 1); REQUIRE(timeline->getTrackCompositionsCount(tid1) == 1); REQUIRE(timeline->getCompositionTrackId(cid1) == tid2); REQUIRE(timeline->getCompositionPosition(cid1) == pos); REQUIRE(timeline->getCompositionTrackId(cid2) == tid1); REQUIRE(timeline->getCompositionPosition(cid2) == pos2); REQUIRE_FALSE(timeline->requestCompositionMove(cid1, tid1, pos2 - 2)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackCompositionsCount(tid2) == 1); REQUIRE(timeline->getTrackCompositionsCount(tid1) == 1); REQUIRE(timeline->getCompositionTrackId(cid1) == tid2); REQUIRE(timeline->getCompositionPosition(cid1) == pos); REQUIRE(timeline->getCompositionTrackId(cid2) == tid1); REQUIRE(timeline->getCompositionPosition(cid2) == pos2); REQUIRE(timeline->requestCompositionMove(cid1, tid1, 0)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackCompositionsCount(tid2) == 0); REQUIRE(timeline->getTrackCompositionsCount(tid1) == 2); REQUIRE(timeline->getCompositionTrackId(cid1) == tid1); REQUIRE(timeline->getCompositionPosition(cid1) == 0); REQUIRE(timeline->getCompositionTrackId(cid2) == tid1); REQUIRE(timeline->getCompositionPosition(cid2) == pos2); } SECTION("Insert consecutive compositions") { int length = 12; REQUIRE(timeline->requestItemResize(cid1, length, true) > -1); REQUIRE(timeline->requestItemResize(cid2, length, true) > -1); REQUIRE(timeline->getCompositionPlaytime(cid1) == length); REQUIRE(timeline->getCompositionPlaytime(cid2) == length); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackCompositionsCount(tid1) == 0); REQUIRE(timeline->getTrackCompositionsCount(tid2) == 0); REQUIRE(timeline->requestCompositionMove(cid1, tid1, 0)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getCompositionTrackId(cid1) == tid1); REQUIRE(timeline->getCompositionPosition(cid1) == 0); REQUIRE(timeline->getTrackCompositionsCount(tid1) == 1); REQUIRE(timeline->requestCompositionMove(cid2, tid1, length)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getCompositionTrackId(cid2) == tid1); REQUIRE(timeline->getCompositionPosition(cid2) == length); REQUIRE(timeline->getTrackCompositionsCount(tid1) == 2); } SECTION("Resize orphan composition") { int length = 12; REQUIRE(timeline->requestItemResize(cid1, length, true) > -1); REQUIRE(timeline->requestItemResize(cid2, length, true) > -1); REQUIRE(timeline->getCompositionPlaytime(cid1) == length); REQUIRE(timeline->getCompositionPlaytime(cid2) == length); REQUIRE(timeline->getCompositionPlaytime(cid2) == length); REQUIRE(timeline->requestItemResize(cid2, 5, true) > -1); auto inOut = std::pair{0, 4}; REQUIRE(timeline->m_allCompositions[cid2]->getInOut() == inOut); REQUIRE(timeline->getCompositionPlaytime(cid2) == 5); REQUIRE(timeline->requestItemResize(cid2, 10, false) > -1); REQUIRE(timeline->getCompositionPlaytime(cid2) == 10); REQUIRE(timeline->requestItemResize(cid2, length + 1, true) > -1); REQUIRE(timeline->getCompositionPlaytime(cid2) == length + 1); REQUIRE(timeline->requestItemResize(cid2, 2, false) > -1); REQUIRE(timeline->getCompositionPlaytime(cid2) == 2); REQUIRE(timeline->requestItemResize(cid2, length, true) > -1); REQUIRE(timeline->getCompositionPlaytime(cid2) == length); REQUIRE(timeline->requestItemResize(cid2, length - 2, true) > -1); REQUIRE(timeline->getCompositionPlaytime(cid2) == length - 2); REQUIRE(timeline->requestItemResize(cid2, length - 3, true) > -1); REQUIRE(timeline->getCompositionPlaytime(cid2) == length - 3); } SECTION("Resize inserted compositions") { int length = 12; REQUIRE(timeline->requestItemResize(cid1, length, true) > -1); REQUIRE(timeline->requestItemResize(cid2, length, true) > -1); REQUIRE(timeline->requestCompositionMove(cid1, tid1, 0)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->requestItemResize(cid1, 5, true) > -1); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getCompositionPlaytime(cid1) == 5); REQUIRE(timeline->getCompositionPosition(cid1) == 0); REQUIRE(timeline->requestCompositionMove(cid2, tid1, 5)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getCompositionPlaytime(cid2) == length); REQUIRE(timeline->getCompositionPosition(cid2) == 5); REQUIRE(timeline->requestItemResize(cid1, 6, true) == -1); REQUIRE(timeline->requestItemResize(cid1, 6, false) == -1); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->requestItemResize(cid2, length - 5, false) > -1); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getCompositionPosition(cid2) == 10); REQUIRE(timeline->requestItemResize(cid1, 10, true) > -1); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackCompositionsCount(tid1) == 2); } SECTION("Change track of resized compositions") { int length = 12; REQUIRE(timeline->requestItemResize(cid1, length, true) > -1); REQUIRE(timeline->requestItemResize(cid2, length, true) > -1); REQUIRE(timeline->requestCompositionMove(cid2, tid1, 5)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackCompositionsCount(tid1) == 1); REQUIRE(timeline->requestCompositionMove(cid1, tid2, 10)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackCompositionsCount(tid2) == 1); REQUIRE(timeline->requestItemResize(cid1, 5, false) > -1); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->requestCompositionMove(cid1, tid1, 0)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackCompositionsCount(tid1) == 2); REQUIRE(timeline->getTrackCompositionsCount(tid2) == 0); } SECTION("Composition Move") { int length = 12; REQUIRE(timeline->requestItemResize(cid1, length, true) > -1); REQUIRE(timeline->requestItemResize(cid2, length, true) > -1); REQUIRE(timeline->requestCompositionMove(cid2, tid1, 5)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackCompositionsCount(tid1) == 1); REQUIRE(timeline->getCompositionTrackId(cid2) == tid1); REQUIRE(timeline->getCompositionPosition(cid2) == 5); REQUIRE(timeline->requestCompositionMove(cid1, tid1, 5 + length)); auto state = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackCompositionsCount(tid1) == 2); REQUIRE(timeline->getCompositionTrackId(cid1) == tid1); REQUIRE(timeline->getCompositionTrackId(cid2) == tid1); REQUIRE(timeline->getCompositionPosition(cid1) == 5 + length); REQUIRE(timeline->getCompositionPosition(cid2) == 5); }; state(); REQUIRE_FALSE(timeline->requestCompositionMove(cid1, tid1, 3 + length)); state(); REQUIRE_FALSE(timeline->requestCompositionMove(cid1, tid1, 0)); state(); REQUIRE(timeline->requestCompositionMove(cid2, tid1, 0)); auto state2 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackCompositionsCount(tid1) == 2); REQUIRE(timeline->getCompositionTrackId(cid1) == tid1); REQUIRE(timeline->getCompositionTrackId(cid2) == tid1); REQUIRE(timeline->getCompositionPosition(cid1) == 5 + length); REQUIRE(timeline->getCompositionPosition(cid2) == 0); }; state2(); REQUIRE_FALSE(timeline->requestCompositionMove(cid1, tid1, 0)); state2(); REQUIRE_FALSE(timeline->requestCompositionMove(cid1, tid1, length - 5)); state2(); REQUIRE(timeline->requestCompositionMove(cid1, tid1, length)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackCompositionsCount(tid1) == 2); REQUIRE(timeline->getCompositionTrackId(cid1) == tid1); REQUIRE(timeline->getCompositionTrackId(cid2) == tid1); REQUIRE(timeline->getCompositionPosition(cid1) == length); REQUIRE(timeline->getCompositionPosition(cid2) == 0); REQUIRE(timeline->requestItemResize(cid2, length - 5, true) > -1); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getCompositionTrackId(cid1) == tid1); REQUIRE(timeline->getCompositionTrackId(cid2) == tid1); REQUIRE(timeline->getCompositionPosition(cid1) == length); REQUIRE(timeline->getCompositionPosition(cid2) == 0); // REQUIRE(timeline->allowCompositionMove(cid1, tid1, length - 5)); REQUIRE(timeline->requestCompositionMove(cid1, tid1, length - 5)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackCompositionsCount(tid1) == 2); REQUIRE(timeline->getCompositionTrackId(cid1) == tid1); REQUIRE(timeline->getCompositionTrackId(cid2) == tid1); REQUIRE(timeline->getCompositionPosition(cid1) == length - 5); REQUIRE(timeline->getCompositionPosition(cid2) == 0); REQUIRE(timeline->requestItemResize(cid2, length - 10, false) > -1); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getCompositionTrackId(cid1) == tid1); REQUIRE(timeline->getCompositionTrackId(cid2) == tid1); REQUIRE(timeline->getCompositionPosition(cid1) == length - 5); REQUIRE(timeline->getCompositionPosition(cid2) == 5); REQUIRE_FALSE(timeline->requestCompositionMove(cid1, tid1, 0)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getCompositionTrackId(cid1) == tid1); REQUIRE(timeline->getCompositionTrackId(cid2) == tid1); REQUIRE(timeline->getCompositionPosition(cid1) == length - 5); REQUIRE(timeline->getCompositionPosition(cid2) == 5); REQUIRE(timeline->requestCompositionMove(cid2, tid1, 0)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackCompositionsCount(tid1) == 2); REQUIRE(timeline->getCompositionTrackId(cid1) == tid1); REQUIRE(timeline->getCompositionTrackId(cid2) == tid1); REQUIRE(timeline->getCompositionPosition(cid1) == length - 5); REQUIRE(timeline->getCompositionPosition(cid2) == 0); } SECTION("Move and resize") { int length = 12; REQUIRE(timeline->requestCompositionMove(cid2, tid2, 0)); REQUIRE(timeline->requestItemResize(cid1, length, true) > -1); REQUIRE(timeline->requestItemResize(cid2, length, true) > -1); REQUIRE(timeline->requestCompositionMove(cid1, tid1, 0)); REQUIRE(timeline->getCompositionPosition(cid1) == 0); REQUIRE(timeline->requestItemResize(cid1, length - 2, false, 0) > -1); REQUIRE(timeline->getCompositionPosition(cid1) == 2); REQUIRE(timeline->requestCompositionMove(cid1, tid1, 0)); auto state = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getCompositionTrackId(cid1) == tid1); REQUIRE(timeline->getTrackCompositionsCount(tid1) == 1); REQUIRE(timeline->getCompositionPosition(cid1) == 0); REQUIRE(timeline->getCompositionPlaytime(cid1) == length - 2); }; state(); REQUIRE(timeline->requestItemResize(cid1, length - 4, true) > -1); REQUIRE(timeline->requestCompositionMove(cid2, tid1, length - 4 + 1)); REQUIRE(timeline->requestItemResize(cid2, length - 2, false) > -1); REQUIRE(timeline->requestCompositionMove(cid2, tid1, length - 4 + 1)); auto state2 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getCompositionTrackId(cid1) == tid1); REQUIRE(timeline->getCompositionTrackId(cid2) == tid1); REQUIRE(timeline->getTrackCompositionsCount(tid1) == 2); REQUIRE(timeline->getCompositionPosition(cid1) == 0); REQUIRE(timeline->getCompositionPlaytime(cid1) == length - 4); REQUIRE(timeline->getCompositionPosition(cid2) == length - 4 + 1); REQUIRE(timeline->getCompositionPlaytime(cid2) == length - 2); }; state2(); // the gap between the two clips is 1 frame, we try to resize them by 2 frames REQUIRE(timeline->requestItemResize(cid1, length - 2, true) == -1); state2(); REQUIRE(timeline->requestItemResize(cid2, length, false) == -1); state2(); REQUIRE(timeline->requestCompositionMove(cid2, tid1, length - 4)); auto state3 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getCompositionTrackId(cid1) == tid1); REQUIRE(timeline->getCompositionTrackId(cid2) == tid1); REQUIRE(timeline->getTrackCompositionsCount(tid1) == 2); REQUIRE(timeline->getCompositionPosition(cid1) == 0); REQUIRE(timeline->getCompositionPlaytime(cid1) == length - 4); REQUIRE(timeline->getCompositionPosition(cid2) == length - 4); REQUIRE(timeline->getCompositionPlaytime(cid2) == length - 2); }; state3(); // Now the gap is 0 frames, the resize should still fail REQUIRE(timeline->requestItemResize(cid1, length - 2, true) == -1); state3(); REQUIRE(timeline->requestItemResize(cid2, length, false) == -1); state3(); // We move cid1 out of the way REQUIRE(timeline->requestCompositionMove(cid1, tid2, 0)); // now resize should work REQUIRE(timeline->requestItemResize(cid1, length - 2, true) > -1); REQUIRE(timeline->requestItemResize(cid2, length, false) > -1); } + Logger::print_trace(); } diff --git a/tests/effectstest.cpp b/tests/effectstest.cpp index a8df5df47..bcfc971f6 100644 --- a/tests/effectstest.cpp +++ b/tests/effectstest.cpp @@ -1,116 +1,118 @@ #include "catch.hpp" #include "doc/docundostack.hpp" #include "test_utils.hpp" #include #include #include #include #include #include #include #include "definitions.h" #define private public #define protected public #include "core.h" #include "effects/effectsrepository.hpp" #include "effects/effectstack/model/effectitemmodel.hpp" #include "effects/effectstack/model/effectstackmodel.hpp" Mlt::Profile profile_effects; QString anEffect; TEST_CASE("Effects stack", "[Effects]") { + Logger::clear(); // Create timeline auto binModel = pCore->projectItemModel(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); // Here we do some trickery to enable testing. // We mock the project class so that the undoStack function returns our undoStack Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; // We also mock timeline object to spy few functions and mock others TimelineItemModel tim(&profile_effects, undoStack); Mock timMock(tim); auto timeline = std::shared_ptr(&timMock.get(), [](...) {}); TimelineItemModel::finishConstruct(timeline, guideModel); Fake(Method(timMock, adjustAssetRange)); // Create a request int tid1; REQUIRE(timeline->requestTrackInsertion(-1, tid1)); // Create clip QString binId = createProducer(profile_effects, "red", binModel); int cid1; REQUIRE(timeline->requestClipInsertion(binId, tid1, 100, cid1)); std::shared_ptr clip = binModel->getClipByBinID(binId); auto model = clip->m_effectStack; REQUIRE(model->checkConsistency()); REQUIRE(model->rowCount() == 0); // Check whether repo works QVector> effects = EffectsRepository::get()->getNames(); REQUIRE(!effects.isEmpty()); anEffect = QStringLiteral("sepia"); // effects.first().first; REQUIRE(!anEffect.isEmpty()); SECTION("Create and delete effects") { REQUIRE(model->appendEffect(anEffect)); REQUIRE(model->checkConsistency()); REQUIRE(model->rowCount() == 1); REQUIRE(model->appendEffect(anEffect)); REQUIRE(model->checkConsistency()); REQUIRE(model->rowCount() == 2); undoStack->undo(); REQUIRE(model->checkConsistency()); REQUIRE(model->rowCount() == 1); } SECTION("Create cut with fade in") { auto clipModel = timeline->getClipPtr(cid1)->m_effectStack; REQUIRE(clipModel->rowCount() == 0); clipModel->appendEffect("fade_from_black"); REQUIRE(clipModel->checkConsistency()); REQUIRE(clipModel->rowCount() == 1); int l = timeline->getClipPlaytime(cid1); REQUIRE(TimelineFunctions::requestClipCut(timeline, cid1, 100 + l - 10)); int splitted = timeline->getClipByPosition(tid1, 100 + l - 9); auto splitModel = timeline->getClipPtr(splitted)->m_effectStack; REQUIRE(clipModel->rowCount() == 1); REQUIRE(splitModel->rowCount() == 0); } SECTION("Create cut with fade out") { auto clipModel = timeline->getClipPtr(cid1)->m_effectStack; REQUIRE(clipModel->rowCount() == 0); clipModel->appendEffect("fade_to_black"); REQUIRE(clipModel->checkConsistency()); REQUIRE(clipModel->rowCount() == 1); int l = timeline->getClipPlaytime(cid1); REQUIRE(TimelineFunctions::requestClipCut(timeline, cid1, 100 + l - 10)); int splitted = timeline->getClipByPosition(tid1, 100 + l - 9); auto splitModel = timeline->getClipPtr(splitted)->m_effectStack; REQUIRE(clipModel->rowCount() == 0); REQUIRE(splitModel->rowCount() == 1); } + Logger::print_trace(); } diff --git a/tests/groupstest.cpp b/tests/groupstest.cpp index c90bfdaa8..4d322e8ac 100644 --- a/tests/groupstest.cpp +++ b/tests/groupstest.cpp @@ -1,1284 +1,1286 @@ #include "bin/model/markerlistmodel.hpp" #include "catch.hpp" #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" #pragma GCC diagnostic push #include "fakeit.hpp" #include #include #define private public #define protected public #include "bin/projectclip.h" #include "bin/projectfolder.h" #include "bin/projectitemmodel.h" #include "core.h" #include "doc/docundostack.hpp" #include "project/projectmanager.h" #include "test_utils.hpp" #include "timeline2/model/clipmodel.hpp" #include "timeline2/model/groupsmodel.hpp" #include "timeline2/model/timelineitemmodel.hpp" #include "timeline2/model/timelinemodel.hpp" #include "timeline2/model/trackmodel.hpp" #include #include Mlt::Profile profile_group; TEST_CASE("Functional test of the group hierarchy", "[GroupsModel]") { + Logger::clear(); auto binModel = pCore->projectItemModel(); binModel->clean(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); // Here we do some trickery to enable testing. // We mock the project class so that the undoStack function returns our undoStack Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; std::shared_ptr timeline = TimelineItemModel::construct(&profile_group, guideModel, undoStack); GroupsModel groups(timeline); std::function undo = []() { return true; }; std::function redo = []() { return true; }; for (int i = 0; i < 10; i++) { groups.createGroupItem(i); } SECTION("Test Basic Creation") { for (int i = 0; i < 10; i++) { REQUIRE(groups.getRootId(i) == i); REQUIRE(groups.isLeaf(i)); REQUIRE(groups.getLeaves(i).size() == 1); REQUIRE(groups.getSubtree(i).size() == 1); } } groups.setGroup(0, 1); groups.setGroup(1, 2); groups.setGroup(3, 2); groups.setGroup(9, 3); groups.setGroup(6, 3); groups.setGroup(4, 3); groups.setGroup(7, 3); groups.setGroup(8, 5); SECTION("Test leaf nodes") { std::unordered_set nodes = {1, 2, 3, 5}; for (int i = 0; i < 10; i++) { REQUIRE(groups.isLeaf(i) != (nodes.count(i) > 0)); if (nodes.count(i) == 0) { REQUIRE(groups.getSubtree(i) == std::unordered_set({i})); REQUIRE(groups.getLeaves(i) == std::unordered_set({i})); } } } SECTION("Test leaves retrieving") { REQUIRE(groups.getLeaves(2) == std::unordered_set({0, 4, 6, 7, 9})); REQUIRE(groups.getLeaves(3) == std::unordered_set({4, 6, 7, 9})); REQUIRE(groups.getLeaves(1) == std::unordered_set({0})); REQUIRE(groups.getLeaves(5) == std::unordered_set({8})); } SECTION("Test subtree retrieving") { REQUIRE(groups.getSubtree(2) == std::unordered_set({0, 1, 2, 3, 4, 6, 7, 9})); REQUIRE(groups.getSubtree(3) == std::unordered_set({3, 4, 6, 7, 9})); REQUIRE(groups.getSubtree(5) == std::unordered_set({5, 8})); } SECTION("Test root retieving") { std::set first_tree = {0, 1, 2, 3, 4, 6, 7, 9}; for (int n : first_tree) { CAPTURE(n); REQUIRE(groups.getRootId(n) == 2); } std::unordered_set second_tree = {5, 8}; for (int n : second_tree) { REQUIRE(groups.getRootId(n) == 5); } } groups.setGroup(3, 8); SECTION("Test leaf nodes 2") { std::unordered_set nodes = {1, 2, 3, 5, 8}; for (int i = 0; i < 10; i++) { REQUIRE(groups.isLeaf(i) != (nodes.count(i) > 0)); if (nodes.count(i) == 0) { REQUIRE(groups.getSubtree(i) == std::unordered_set({i})); REQUIRE(groups.getLeaves(i) == std::unordered_set({i})); } } } SECTION("Test leaves retrieving 2") { REQUIRE(groups.getLeaves(1) == std::unordered_set({0})); REQUIRE(groups.getLeaves(2) == std::unordered_set({0})); REQUIRE(groups.getLeaves(3) == std::unordered_set({4, 6, 7, 9})); REQUIRE(groups.getLeaves(5) == std::unordered_set({4, 6, 7, 9})); REQUIRE(groups.getLeaves(8) == std::unordered_set({4, 6, 7, 9})); } SECTION("Test subtree retrieving 2") { REQUIRE(groups.getSubtree(2) == std::unordered_set({0, 1, 2})); REQUIRE(groups.getSubtree(3) == std::unordered_set({3, 4, 6, 7, 9})); REQUIRE(groups.getSubtree(5) == std::unordered_set({5, 8, 3, 4, 6, 7, 9})); } SECTION("Test root retieving 2") { std::set first_tree = {0, 1, 2}; for (int n : first_tree) { CAPTURE(n); REQUIRE(groups.getRootId(n) == 2); } std::unordered_set second_tree = {5, 8, 3, 4, 6, 7, 9}; for (int n : second_tree) { REQUIRE(groups.getRootId(n) == 5); } } groups.setGroup(5, 2); SECTION("Test leaf nodes 3") { std::unordered_set nodes = {1, 2, 3, 5, 8}; for (int i = 0; i < 10; i++) { REQUIRE(groups.isLeaf(i) != (nodes.count(i) > 0)); if (nodes.count(i) == 0) { REQUIRE(groups.getSubtree(i) == std::unordered_set({i})); REQUIRE(groups.getLeaves(i) == std::unordered_set({i})); } } } SECTION("Test leaves retrieving 3") { REQUIRE(groups.getLeaves(1) == std::unordered_set({0})); REQUIRE(groups.getLeaves(2) == std::unordered_set({0, 4, 6, 7, 9})); REQUIRE(groups.getLeaves(3) == std::unordered_set({4, 6, 7, 9})); REQUIRE(groups.getLeaves(5) == std::unordered_set({4, 6, 7, 9})); REQUIRE(groups.getLeaves(8) == std::unordered_set({4, 6, 7, 9})); } SECTION("Test subtree retrieving 3") { REQUIRE(groups.getSubtree(2) == std::unordered_set({0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); REQUIRE(groups.getSubtree(3) == std::unordered_set({3, 4, 6, 7, 9})); REQUIRE(groups.getSubtree(5) == std::unordered_set({5, 8, 3, 4, 6, 7, 9})); } SECTION("Test root retieving 3") { for (int i = 0; i < 10; i++) { CAPTURE(i); REQUIRE(groups.getRootId(i) == 2); } } groups.destructGroupItem(8, false, undo, redo); SECTION("Test leaf nodes 4") { std::unordered_set nodes = {1, 2, 3}; for (int i = 0; i < 10; i++) { if (i == 8) continue; REQUIRE(groups.isLeaf(i) != (nodes.count(i) > 0)); if (nodes.count(i) == 0) { REQUIRE(groups.getSubtree(i) == std::unordered_set({i})); REQUIRE(groups.getLeaves(i) == std::unordered_set({i})); } } } SECTION("Test leaves retrieving 4") { REQUIRE(groups.getLeaves(1) == std::unordered_set({0})); REQUIRE(groups.getLeaves(2) == std::unordered_set({0, 5})); REQUIRE(groups.getLeaves(3) == std::unordered_set({4, 6, 7, 9})); } SECTION("Test subtree retrieving 4") { REQUIRE(groups.getSubtree(2) == std::unordered_set({0, 1, 2, 5})); REQUIRE(groups.getSubtree(3) == std::unordered_set({3, 4, 6, 7, 9})); REQUIRE(groups.getSubtree(5) == std::unordered_set({5})); } SECTION("Test root retieving 4") { std::set first_tree = {0, 1, 2, 5}; for (int n : first_tree) { CAPTURE(n); REQUIRE(groups.getRootId(n) == 2); } std::unordered_set second_tree = {3, 4, 6, 7, 9}; for (int n : second_tree) { CAPTURE(n); REQUIRE(groups.getRootId(n) == 3); } } + Logger::print_trace(); } TEST_CASE("Interface test of the group hierarchy", "[GroupsModel]") { auto binModel = pCore->projectItemModel(); binModel->clean(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); // Here we do some trickery to enable testing. // We mock the project class so that the undoStack function returns our undoStack Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; std::shared_ptr timeline = TimelineItemModel::construct(&profile_group, guideModel, undoStack); GroupsModel groups(timeline); std::function undo = []() { return true; }; std::function redo = []() { return true; }; for (int i = 0; i < 10; i++) { groups.createGroupItem(i); // the following call shouldn't do anything, but we test that behaviour too. groups.ungroupItem(i, undo, redo); REQUIRE(groups.getRootId(i) == i); REQUIRE(groups.isLeaf(i)); REQUIRE(groups.getLeaves(i).size() == 1); REQUIRE(groups.getSubtree(i).size() == 1); REQUIRE(groups.checkConsistency(false)); } auto g1 = std::unordered_set({4, 6, 7, 9}); int gid1 = groups.groupItems(g1, undo, redo); SECTION("One single group") { for (int i = 0; i < 10; i++) { if (g1.count(i) > 0) { REQUIRE(groups.getRootId(i) == gid1); } else { REQUIRE(groups.getRootId(i) == i); } REQUIRE(groups.getSubtree(i) == std::unordered_set({i})); REQUIRE(groups.getLeaves(i) == std::unordered_set({i})); } REQUIRE(groups.getLeaves(gid1) == g1); auto g1b = g1; g1b.insert(gid1); REQUIRE(groups.getSubtree(gid1) == g1b); REQUIRE(groups.checkConsistency(false)); } SECTION("Twice the same group") { int old_gid1 = gid1; gid1 = groups.groupItems(g1, undo, redo); // recreate the same group (will create a parent with the old group as only element) for (int i = 0; i < 10; i++) { if (g1.count(i) > 0) { REQUIRE(groups.getRootId(i) == gid1); } else { REQUIRE(groups.getRootId(i) == i); } REQUIRE(groups.getSubtree(i) == std::unordered_set({i})); REQUIRE(groups.getLeaves(i) == std::unordered_set({i})); } REQUIRE(groups.getLeaves(gid1) == g1); REQUIRE(groups.getLeaves(old_gid1) == g1); auto g1b = g1; g1b.insert(old_gid1); REQUIRE(groups.getSubtree(old_gid1) == g1b); g1b.insert(gid1); REQUIRE(groups.getSubtree(gid1) == g1b); REQUIRE(groups.checkConsistency(false)); } auto g2 = std::unordered_set({3, 5, 7}); int gid2 = groups.groupItems(g2, undo, redo); auto all_g2 = g2; all_g2.insert(4); all_g2.insert(6); all_g2.insert(9); SECTION("Heterogeneous group") { for (int i = 0; i < 10; i++) { CAPTURE(i); if (all_g2.count(i) > 0) { REQUIRE(groups.getRootId(i) == gid2); } else { REQUIRE(groups.getRootId(i) == i); } REQUIRE(groups.getSubtree(i) == std::unordered_set({i})); REQUIRE(groups.getLeaves(i) == std::unordered_set({i})); } REQUIRE(groups.getLeaves(gid1) == g1); REQUIRE(groups.getLeaves(gid2) == all_g2); REQUIRE(groups.checkConsistency(false)); } auto g3 = std::unordered_set({0, 1}); int gid3 = groups.groupItems(g3, undo, redo); auto g4 = std::unordered_set({0, 4}); int gid4 = groups.groupItems(g4, undo, redo); auto all_g4 = all_g2; for (int i : g3) all_g4.insert(i); SECTION("Group of group") { for (int i = 0; i < 10; i++) { CAPTURE(i); if (all_g4.count(i) > 0) { REQUIRE(groups.getRootId(i) == gid4); } else { REQUIRE(groups.getRootId(i) == i); } REQUIRE(groups.getSubtree(i) == std::unordered_set({i})); REQUIRE(groups.getLeaves(i) == std::unordered_set({i})); } REQUIRE(groups.getLeaves(gid1) == g1); REQUIRE(groups.getLeaves(gid2) == all_g2); REQUIRE(groups.getLeaves(gid3) == g3); REQUIRE(groups.getLeaves(gid4) == all_g4); REQUIRE(groups.checkConsistency(false)); } // the following should delete g4 groups.ungroupItem(3, undo, redo); SECTION("Ungroup") { for (int i = 0; i < 10; i++) { CAPTURE(i); if (all_g2.count(i) > 0) { REQUIRE(groups.getRootId(i) == gid2); } else if (g3.count(i) > 0) { REQUIRE(groups.getRootId(i) == gid3); } else { REQUIRE(groups.getRootId(i) == i); } REQUIRE(groups.getSubtree(i) == std::unordered_set({i})); REQUIRE(groups.getLeaves(i) == std::unordered_set({i})); } REQUIRE(groups.getLeaves(gid1) == g1); REQUIRE(groups.checkConsistency(false)); REQUIRE(groups.getLeaves(gid2) == all_g2); REQUIRE(groups.getLeaves(gid3) == g3); } } TEST_CASE("Orphan groups deletion", "[GroupsModel]") { auto binModel = pCore->projectItemModel(); binModel->clean(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); // Here we do some trickery to enable testing. // We mock the project class so that the undoStack function returns our undoStack Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; std::shared_ptr timeline = TimelineItemModel::construct(&profile_group, guideModel, undoStack); GroupsModel groups(timeline); std::function undo = []() { return true; }; std::function redo = []() { return true; }; for (int i = 0; i < 4; i++) { groups.createGroupItem(i); } auto g1 = std::unordered_set({0, 1}); int gid1 = groups.groupItems(g1, undo, redo); auto g2 = std::unordered_set({2, 3}); int gid2 = groups.groupItems(g2, undo, redo); Q_UNUSED(gid2); auto g3 = std::unordered_set({0, 3}); int gid3 = groups.groupItems(g3, undo, redo); REQUIRE(groups.getLeaves(gid3) == std::unordered_set({0, 1, 2, 3})); REQUIRE(groups.checkConsistency(false)); groups.destructGroupItem(0, true, undo, redo); REQUIRE(groups.getLeaves(gid3) == std::unordered_set({1, 2, 3})); REQUIRE(groups.checkConsistency(false)); SECTION("Normal deletion") { groups.destructGroupItem(1, false, undo, redo); REQUIRE(groups.getLeaves(gid3) == std::unordered_set({gid1, 2, 3})); REQUIRE(groups.checkConsistency(false)); groups.destructGroupItem(gid1, true, undo, redo); REQUIRE(groups.getLeaves(gid3) == std::unordered_set({2, 3})); REQUIRE(groups.checkConsistency(false)); } SECTION("Cascade deletion") { groups.destructGroupItem(1, true, undo, redo); REQUIRE(groups.getLeaves(gid3) == std::unordered_set({2, 3})); REQUIRE(groups.checkConsistency(false)); groups.destructGroupItem(2, true, undo, redo); REQUIRE(groups.getLeaves(gid3) == std::unordered_set({3})); REQUIRE(groups.checkConsistency(false)); REQUIRE(groups.m_downLink.count(gid3) > 0); groups.destructGroupItem(3, true, undo, redo); REQUIRE(groups.m_downLink.count(gid3) == 0); REQUIRE(groups.m_downLink.size() == 0); REQUIRE(groups.checkConsistency(false)); } } TEST_CASE("Integration with timeline", "[GroupsModel]") { qDebug() << "STARTING PASS"; auto binModel = pCore->projectItemModel(); binModel->clean(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); // Here we do some trickery to enable testing. // We mock the project class so that the undoStack function returns our undoStack Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; TimelineItemModel tim(&profile_group, undoStack); Mock timMock(tim); auto timeline = std::shared_ptr(&timMock.get(), [](...) {}); TimelineItemModel::finishConstruct(timeline, guideModel); TimelineItemModel tim2(&profile_group, undoStack); Mock timMock2(tim2); auto timeline2 = std::shared_ptr(&timMock2.get(), [](...) {}); TimelineItemModel::finishConstruct(timeline2, guideModel); RESET(timMock); RESET(timMock2); QString binId = createProducer(profile_group, "red", binModel); QString binId2 = createProducerWithSound(profile_group, binModel); int length = binModel->getClipByBinID(binId)->frameDuration(); GroupsModel groups(timeline); std::vector clips; for (int i = 0; i < 4; i++) { if (i % 2 == 0) { clips.push_back(ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly)); } else { clips.push_back(ClipModel::construct(timeline, binId, -1, PlaylistState::AudioOnly)); timeline->m_allClips[clips.back()]->m_canBeAudio = true; } } std::vector clips2; for (int i = 0; i < 4; i++) { if (i % 2 == 0) { clips2.push_back(ClipModel::construct(timeline2, binId, -1, PlaylistState::VideoOnly)); } else { clips2.push_back(ClipModel::construct(timeline2, binId, -1, PlaylistState::AudioOnly)); timeline2->m_allClips[clips2.back()]->m_canBeAudio = true; } } int tid1 = TrackModel::construct(timeline); int tid2 = TrackModel::construct(timeline); Q_UNUSED(tid2); int tid3 = TrackModel::construct(timeline, -1, -1, QStringLiteral("audio"), true); int tid1_2 = TrackModel::construct(timeline2); int tid2_2 = TrackModel::construct(timeline2); Q_UNUSED(tid2_2); int tid3_2 = TrackModel::construct(timeline2, -1, -1, QStringLiteral("audio2"), true); int init_index = undoStack->index(); SECTION("Basic Creation and export/import from json") { auto check_roots = [&](int r1, int r2, int r3, int r4) { REQUIRE(timeline->m_groups->getRootId(clips[0]) == r1); REQUIRE(timeline->m_groups->getRootId(clips[1]) == r2); REQUIRE(timeline->m_groups->getRootId(clips[2]) == r3); REQUIRE(timeline->m_groups->getRootId(clips[3]) == r4); }; // the following function is a recursive function to check the correctness of a json import // Basically, it takes as input a groupId in the imported (target) group hierarchy, and outputs the corresponding groupId from the original one. If no // match is found, it returns -1 std::function rec_check; rec_check = [&](int gid) { // we first check if the gid is a leaf if (timeline2->m_groups->isLeaf(gid)) { // then it must be a clip/composition int found = -1; for (int i = 0; i < 4; i++) { if (clips2[i] == gid) { found = i; break; } } if (found != -1) { return clips[found]; } else { qDebug() << "ERROR: did not find correspondence for group" << gid; } } else { // we find correspondances of all the children auto children = timeline2->m_groups->getDirectChildren(gid); std::unordered_set corresp; for (int c : children) { corresp.insert(rec_check(c)); } if (corresp.count(-1) > 0) { return -1; // something went wrong } std::unordered_set parents; for (int c : corresp) { // we find the parents of the corresponding groups in the original hierarchy parents.insert(timeline->m_groups->m_upLink[c]); } // if the matching is correct, we should have found only one parent if (parents.size() != 1) { return -1; // something went wrong } return *parents.begin(); } return -1; }; auto checkJsonParsing = [&]() { // we first destroy all groups in target timeline Fun undo = []() { return true; }; Fun redo = []() { return true; }; for (int i = 0; i < 4; i++) { while (timeline2->m_groups->getRootId(clips2[i]) != clips2[i]) { timeline2->m_groups->ungroupItem(clips2[i], undo, redo); } } // we do the export then import REQUIRE(timeline2->m_groups->fromJson(timeline->m_groups->toJson())); std::unordered_map roots; for (int i = 0; i < 4; i++) { int r = timeline2->m_groups->getRootId(clips2[0]); if (roots.count(r) == 0) { roots[r] = rec_check(r); REQUIRE(roots[r] != -1); } } for (int i = 0; i < 4; i++) { int r = timeline->m_groups->getRootId(clips[0]); int r2 = timeline2->m_groups->getRootId(clips2[0]); REQUIRE(roots[r2] == r); } REQUIRE(timeline->checkConsistency()); REQUIRE(timeline2->checkConsistency()); }; REQUIRE(timeline->checkConsistency()); REQUIRE(timeline2->checkConsistency()); auto g1 = std::unordered_set({clips[0], clips[1]}); int gid1, gid2, gid3; // this fails because clips are not inserted REQUIRE(timeline->requestClipsGroup(g1) == -1); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline2->checkConsistency()); for (int i = 0; i < 4; i++) { REQUIRE(timeline->requestClipMove(clips[i], (i % 2 == 0) ? tid1 : tid3, i * length)); } for (int i = 0; i < 4; i++) { REQUIRE(timeline2->requestClipMove(clips2[i], (i % 2 == 0) ? tid1_2 : tid3_2, i * length)); } REQUIRE(timeline->checkConsistency()); REQUIRE(timeline2->checkConsistency()); init_index = undoStack->index(); REQUIRE(timeline->requestClipsGroup(g1, true, GroupType::Normal) > 0); auto state1 = [&]() { gid1 = timeline->m_groups->getRootId(clips[0]); check_roots(gid1, gid1, clips[2], clips[3]); REQUIRE(timeline->m_groups->getType(gid1) == GroupType::Normal); REQUIRE(timeline->m_groups->getSubtree(gid1) == std::unordered_set({gid1, clips[0], clips[1]})); REQUIRE(timeline->m_groups->getLeaves(gid1) == std::unordered_set({clips[0], clips[1]})); REQUIRE(undoStack->index() == init_index + 1); REQUIRE(timeline->checkConsistency()); }; INFO("Test 1"); state1(); checkJsonParsing(); state1(); auto g2 = std::unordered_set({clips[2], clips[3]}); REQUIRE(timeline->requestClipsGroup(g2, true, GroupType::AVSplit) > 0); auto state2 = [&]() { gid2 = timeline->m_groups->getRootId(clips[2]); check_roots(gid1, gid1, gid2, gid2); REQUIRE(timeline->m_groups->getType(gid1) == GroupType::Normal); REQUIRE(timeline->m_groups->getType(gid2) == GroupType::AVSplit); REQUIRE(timeline->m_groups->getSubtree(gid2) == std::unordered_set({gid2, clips[2], clips[3]})); REQUIRE(timeline->m_groups->getLeaves(gid2) == std::unordered_set({clips[2], clips[3]})); REQUIRE(timeline->m_groups->getSubtree(gid1) == std::unordered_set({gid1, clips[0], clips[1]})); REQUIRE(timeline->m_groups->getLeaves(gid1) == std::unordered_set({clips[0], clips[1]})); REQUIRE(undoStack->index() == init_index + 2); REQUIRE(timeline->checkConsistency()); }; INFO("Test 2"); checkJsonParsing(); state2(); auto g3 = std::unordered_set({clips[0], clips[3]}); REQUIRE(timeline->requestClipsGroup(g3, true, GroupType::Normal) > 0); auto state3 = [&]() { REQUIRE(undoStack->index() == init_index + 3); gid3 = timeline->m_groups->getRootId(clips[0]); check_roots(gid3, gid3, gid3, gid3); REQUIRE(timeline->m_groups->getType(gid1) == GroupType::Normal); REQUIRE(timeline->m_groups->getType(gid2) == GroupType::AVSplit); REQUIRE(timeline->m_groups->getType(gid3) == GroupType::Normal); REQUIRE(timeline->m_groups->getSubtree(gid3) == std::unordered_set({gid1, clips[0], clips[1], gid3, gid2, clips[2], clips[3]})); REQUIRE(timeline->m_groups->getLeaves(gid3) == std::unordered_set({clips[2], clips[3], clips[0], clips[1]})); REQUIRE(timeline->m_groups->getSubtree(gid2) == std::unordered_set({gid2, clips[2], clips[3]})); REQUIRE(timeline->m_groups->getLeaves(gid2) == std::unordered_set({clips[2], clips[3]})); REQUIRE(timeline->m_groups->getSubtree(gid1) == std::unordered_set({gid1, clips[0], clips[1]})); REQUIRE(timeline->m_groups->getLeaves(gid1) == std::unordered_set({clips[0], clips[1]})); REQUIRE(timeline->checkConsistency()); }; INFO("Test 3"); checkJsonParsing(); state3(); undoStack->undo(); INFO("Test 4"); checkJsonParsing(); state2(); undoStack->redo(); INFO("Test 5"); checkJsonParsing(); state3(); undoStack->undo(); INFO("Test 6"); checkJsonParsing(); state2(); undoStack->undo(); INFO("Test 8"); checkJsonParsing(); state1(); undoStack->undo(); INFO("Test 9"); checkJsonParsing(); check_roots(clips[0], clips[1], clips[2], clips[3]); undoStack->redo(); INFO("Test 10"); checkJsonParsing(); state1(); undoStack->redo(); INFO("Test 11"); checkJsonParsing(); state2(); REQUIRE(timeline->requestClipsGroup(g3) > 0); checkJsonParsing(); state3(); undoStack->undo(); checkJsonParsing(); state2(); undoStack->undo(); checkJsonParsing(); state1(); undoStack->undo(); checkJsonParsing(); check_roots(clips[0], clips[1], clips[2], clips[3]); } SECTION("Group deletion undo") { CAPTURE(clips[0]); CAPTURE(clips[1]); CAPTURE(clips[2]); CAPTURE(clips[3]); REQUIRE(timeline->requestClipMove(clips[0], tid1, 10)); REQUIRE(timeline->requestClipMove(clips[1], tid3, 10 + length)); REQUIRE(timeline->requestClipMove(clips[2], tid1, 15 + 2 * length)); REQUIRE(timeline->requestClipMove(clips[3], tid3, 50 + 3 * length)); auto state0 = [&]() { REQUIRE(timeline->getTrackById(tid1)->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 2); REQUIRE(timeline->getTrackClipsCount(tid3) == 2); REQUIRE(timeline->getClipsCount() == 4); REQUIRE(timeline->getClipTrackId(clips[0]) == tid1); REQUIRE(timeline->getClipTrackId(clips[2]) == tid1); REQUIRE(timeline->getClipTrackId(clips[1]) == tid3); REQUIRE(timeline->getClipTrackId(clips[3]) == tid3); REQUIRE(timeline->getClipPosition(clips[0]) == 10); REQUIRE(timeline->getClipPosition(clips[1]) == 10 + length); REQUIRE(timeline->getClipPosition(clips[2]) == 15 + 2 * length); REQUIRE(timeline->getClipPosition(clips[3]) == 50 + 3 * length); REQUIRE(timeline->checkConsistency()); }; auto state = [&](int gid1, int gid2, int gid3) { state0(); REQUIRE(timeline->m_groups->getType(gid1) == GroupType::AVSplit); REQUIRE(timeline->m_groups->getType(gid2) == GroupType::AVSplit); REQUIRE(timeline->m_groups->getType(gid3) == GroupType::Normal); REQUIRE(timeline->checkConsistency()); }; state0(); auto g1 = std::unordered_set({clips[0], clips[1]}); int gid1, gid2, gid3; gid1 = timeline->requestClipsGroup(g1, true, GroupType::AVSplit); REQUIRE(gid1 > 0); auto g2 = std::unordered_set({clips[2], clips[3]}); gid2 = timeline->requestClipsGroup(g2, true, GroupType::AVSplit); REQUIRE(gid2 > 0); auto g3 = std::unordered_set({clips[0], clips[3]}); gid3 = timeline->requestClipsGroup(g3, true, GroupType::Normal); REQUIRE(gid3 > 0); state(gid1, gid2, gid3); std::vector all_clips{clips[0], clips[2], clips[1], clips[3]}; for (int i = 0; i < 4; i++) { REQUIRE(timeline->requestItemDeletion(all_clips[i])); REQUIRE(timeline->getTrackClipsCount(tid1) == 0); REQUIRE(timeline->getTrackClipsCount(tid3) == 0); REQUIRE(timeline->getClipsCount() == 0); REQUIRE(timeline->getTrackById(tid1)->checkConsistency()); REQUIRE(timeline->getTrackById(tid3)->checkConsistency()); REQUIRE(timeline->checkConsistency()); undoStack->undo(); state(gid1, gid2, gid3); undoStack->redo(); REQUIRE(timeline->getTrackClipsCount(tid1) == 0); REQUIRE(timeline->getTrackClipsCount(tid3) == 0); REQUIRE(timeline->getClipsCount() == 0); REQUIRE(timeline->checkConsistency()); undoStack->undo(); state(gid1, gid2, gid3); } // we undo the three grouping operations undoStack->undo(); state0(); undoStack->undo(); state0(); undoStack->undo(); state0(); } SECTION("Group creation and query from timeline") { REQUIRE(timeline->requestClipMove(clips[0], tid1, 10)); REQUIRE(timeline->requestClipMove(clips[1], tid1, 10 + length)); REQUIRE(timeline->requestClipMove(clips[2], tid1, 15 + 2 * length)); REQUIRE(timeline->requestClipMove(clips[3], tid1, 50 + 3 * length)); auto state1 = [&]() { REQUIRE(timeline->getGroupElements(clips[2]) == std::unordered_set({clips[2]})); REQUIRE(timeline->getGroupElements(clips[1]) == std::unordered_set({clips[1]})); REQUIRE(timeline->getGroupElements(clips[3]) == std::unordered_set({clips[3]})); REQUIRE(timeline->getGroupElements(clips[0]) == std::unordered_set({clips[0]})); REQUIRE(timeline->checkConsistency()); }; state1(); auto g1 = std::unordered_set({clips[0], clips[3]}); REQUIRE(timeline->requestClipsGroup(g1) > 0); auto state2 = [&]() { REQUIRE(timeline->getGroupElements(clips[0]) == g1); REQUIRE(timeline->getGroupElements(clips[3]) == g1); REQUIRE(timeline->getGroupElements(clips[2]) == std::unordered_set({clips[2]})); REQUIRE(timeline->getGroupElements(clips[1]) == std::unordered_set({clips[1]})); REQUIRE(timeline->checkConsistency()); }; state2(); undoStack->undo(); state1(); undoStack->redo(); state2(); undoStack->undo(); state1(); undoStack->redo(); state2(); auto g2 = std::unordered_set({clips[2], clips[1]}); REQUIRE(timeline->requestClipsGroup(g2) > 0); auto state3 = [&]() { REQUIRE(timeline->getGroupElements(clips[0]) == g1); REQUIRE(timeline->getGroupElements(clips[3]) == g1); REQUIRE(timeline->getGroupElements(clips[2]) == g2); REQUIRE(timeline->getGroupElements(clips[1]) == g2); REQUIRE(timeline->checkConsistency()); }; state3(); undoStack->undo(); state2(); undoStack->redo(); state3(); auto g3 = std::unordered_set({clips[0], clips[1]}); REQUIRE(timeline->requestClipsGroup(g3) > 0); auto all_g = std::unordered_set({clips[0], clips[1], clips[2], clips[3]}); auto state4 = [&]() { REQUIRE(timeline->getGroupElements(clips[0]) == all_g); REQUIRE(timeline->getGroupElements(clips[3]) == all_g); REQUIRE(timeline->getGroupElements(clips[2]) == all_g); REQUIRE(timeline->getGroupElements(clips[1]) == all_g); REQUIRE(timeline->checkConsistency()); }; state4(); undoStack->undo(); state3(); undoStack->redo(); state4(); REQUIRE(timeline->requestClipUngroup(clips[0])); state3(); undoStack->undo(); state4(); REQUIRE(timeline->requestClipUngroup(clips[1])); state3(); undoStack->undo(); state4(); undoStack->redo(); state3(); REQUIRE(timeline->requestClipUngroup(clips[0])); REQUIRE(timeline->getGroupElements(clips[2]) == g2); REQUIRE(timeline->getGroupElements(clips[1]) == g2); REQUIRE(timeline->getGroupElements(clips[3]) == std::unordered_set({clips[3]})); REQUIRE(timeline->getGroupElements(clips[0]) == std::unordered_set({clips[0]})); REQUIRE(timeline->requestClipUngroup(clips[1])); state1(); } } TEST_CASE("Complex Functions", "[GroupsModel]") { auto binModel = pCore->projectItemModel(); binModel->clean(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); // Here we do some trickery to enable testing. // We mock the project class so that the undoStack function returns our undoStack Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; TimelineItemModel tim(&profile_group, undoStack); Mock timMock(tim); auto timeline = std::shared_ptr(&timMock.get(), [](...) {}); TimelineItemModel::finishConstruct(timeline, guideModel); RESET(timMock); GroupsModel groups(timeline); SECTION("MergeSingleGroups") { Fun undo = []() { return true; }; Fun redo = []() { return true; }; REQUIRE(groups.m_upLink.size() == 0); for (int i = 0; i < 6; i++) { groups.createGroupItem(i); } groups.setGroup(0, 3); groups.setGroup(2, 4); groups.setGroup(3, 1); groups.setGroup(4, 1); groups.setGroup(5, 0); auto test_tree = [&]() { REQUIRE(groups.getSubtree(1) == std::unordered_set({0, 1, 2, 3, 4, 5})); REQUIRE(groups.getDirectChildren(0) == std::unordered_set({5})); REQUIRE(groups.getDirectChildren(1) == std::unordered_set({3, 4})); REQUIRE(groups.getDirectChildren(2) == std::unordered_set({})); REQUIRE(groups.getDirectChildren(3) == std::unordered_set({0})); REQUIRE(groups.getDirectChildren(4) == std::unordered_set({2})); REQUIRE(groups.getDirectChildren(5) == std::unordered_set({})); REQUIRE(groups.checkConsistency(false)); }; test_tree(); REQUIRE(groups.mergeSingleGroups(1, undo, redo)); auto test_tree2 = [&]() { REQUIRE(groups.getSubtree(1) == std::unordered_set({1, 2, 5})); REQUIRE(groups.getDirectChildren(1) == std::unordered_set({2, 5})); REQUIRE(groups.getDirectChildren(2) == std::unordered_set({})); REQUIRE(groups.getDirectChildren(5) == std::unordered_set({})); REQUIRE(groups.checkConsistency()); }; test_tree2(); undo(); test_tree(); redo(); test_tree2(); } SECTION("MergeSingleGroups2") { Fun undo = []() { return true; }; Fun redo = []() { return true; }; REQUIRE(groups.m_upLink.size() == 0); for (int i = 0; i < 3; i++) { groups.createGroupItem(i); } groups.setGroup(1, 0); groups.setGroup(2, 1); auto test_tree = [&]() { REQUIRE(groups.getSubtree(0) == std::unordered_set({0, 1, 2})); REQUIRE(groups.getDirectChildren(0) == std::unordered_set({1})); REQUIRE(groups.getDirectChildren(1) == std::unordered_set({2})); REQUIRE(groups.getDirectChildren(2) == std::unordered_set({})); REQUIRE(groups.checkConsistency(false)); }; test_tree(); REQUIRE(groups.mergeSingleGroups(0, undo, redo)); auto test_tree2 = [&]() { REQUIRE(groups.getSubtree(2) == std::unordered_set({2})); REQUIRE(groups.getDirectChildren(2) == std::unordered_set({})); REQUIRE(groups.getRootId(2) == 2); REQUIRE(groups.checkConsistency()); }; test_tree2(); undo(); test_tree(); redo(); test_tree2(); } SECTION("MergeSingleGroups3") { Fun undo = []() { return true; }; Fun redo = []() { return true; }; REQUIRE(groups.m_upLink.size() == 0); for (int i = 0; i < 6; i++) { groups.createGroupItem(i); } groups.setGroup(0, 2); groups.setGroup(1, 0); groups.setGroup(3, 1); groups.setGroup(4, 1); groups.setGroup(5, 4); auto test_tree = [&]() { for (int i = 0; i < 6; i++) { REQUIRE(groups.getRootId(i) == 2); } REQUIRE(groups.getSubtree(2) == std::unordered_set({0, 1, 2, 3, 4, 5})); REQUIRE(groups.getDirectChildren(0) == std::unordered_set({1})); REQUIRE(groups.getDirectChildren(1) == std::unordered_set({4, 3})); REQUIRE(groups.getDirectChildren(2) == std::unordered_set({0})); REQUIRE(groups.getDirectChildren(3) == std::unordered_set({})); REQUIRE(groups.getDirectChildren(4) == std::unordered_set({5})); REQUIRE(groups.getDirectChildren(5) == std::unordered_set({})); REQUIRE(groups.checkConsistency(false)); }; test_tree(); REQUIRE(groups.mergeSingleGroups(2, undo, redo)); auto test_tree2 = [&]() { REQUIRE(groups.getRootId(1) == 1); REQUIRE(groups.getRootId(3) == 1); REQUIRE(groups.getRootId(5) == 1); REQUIRE(groups.getSubtree(1) == std::unordered_set({1, 3, 5})); REQUIRE(groups.getDirectChildren(1) == std::unordered_set({3, 5})); REQUIRE(groups.getDirectChildren(3) == std::unordered_set({})); REQUIRE(groups.getDirectChildren(5) == std::unordered_set({})); REQUIRE(groups.checkConsistency()); }; test_tree2(); undo(); test_tree(); redo(); test_tree2(); } SECTION("Split leaf") { Fun undo = []() { return true; }; Fun redo = []() { return true; }; REQUIRE(groups.m_upLink.size() == 0); // This is a dummy split criterion auto criterion = [](int a) { return a % 2 == 0; }; auto criterion2 = [](int a) { return a % 2 != 0; }; // We create a leaf groups.createGroupItem(1); auto test_leaf = [&]() { REQUIRE(groups.getRootId(1) == 1); REQUIRE(groups.isLeaf(1)); REQUIRE(groups.m_upLink.size() == 1); REQUIRE(groups.checkConsistency()); }; test_leaf(); REQUIRE(groups.split(1, criterion, undo, redo)); test_leaf(); undo(); test_leaf(); redo(); REQUIRE(groups.split(1, criterion2, undo, redo)); test_leaf(); undo(); test_leaf(); redo(); } SECTION("Simple split Tree") { Fun undo = []() { return true; }; Fun redo = []() { return true; }; REQUIRE(groups.m_upLink.size() == 0); // This is a dummy split criterion auto criterion = [](int a) { return a % 2 == 0; }; // We create a very simple tree for (int i = 0; i < 3; i++) { groups.createGroupItem(i); } groups.setGroup(1, 0); groups.setGroup(2, 0); auto test_tree = [&]() { REQUIRE(groups.getRootId(0) == 0); REQUIRE(groups.getRootId(1) == 0); REQUIRE(groups.getRootId(2) == 0); REQUIRE(groups.getSubtree(0) == std::unordered_set({0, 1, 2})); REQUIRE(groups.getDirectChildren(0) == std::unordered_set({1, 2})); REQUIRE(groups.getDirectChildren(1) == std::unordered_set({})); REQUIRE(groups.getDirectChildren(2) == std::unordered_set({})); REQUIRE(groups.checkConsistency()); }; test_tree(); REQUIRE(groups.split(0, criterion, undo, redo)); auto test_tree2 = [&]() { REQUIRE(groups.getRootId(1) == 1); REQUIRE(groups.getRootId(2) == 2); REQUIRE(groups.getSubtree(2) == std::unordered_set({2})); REQUIRE(groups.getSubtree(1) == std::unordered_set({1})); REQUIRE(groups.getDirectChildren(2) == std::unordered_set({})); REQUIRE(groups.getDirectChildren(1) == std::unordered_set({})); REQUIRE(groups.checkConsistency()); }; test_tree2(); undo(); test_tree(); redo(); test_tree2(); } SECTION("complex split Tree") { Fun undo = []() { return true; }; Fun redo = []() { return true; }; REQUIRE(groups.m_upLink.size() == 0); // This is a dummy split criterion auto criterion = [](int a) { return a % 2 != 0; }; for (int i = 0; i < 9; i++) { groups.createGroupItem(i); } groups.setGroup(0, 3); groups.setGroup(1, 0); groups.setGroup(3, 2); groups.setGroup(4, 3); groups.setGroup(5, 8); groups.setGroup(6, 0); groups.setGroup(7, 8); groups.setGroup(8, 2); auto test_tree = [&]() { for (int i = 0; i < 9; i++) { REQUIRE(groups.getRootId(i) == 2); } REQUIRE(groups.getSubtree(2) == std::unordered_set({0, 1, 2, 3, 4, 5, 6, 7, 8})); REQUIRE(groups.getDirectChildren(0) == std::unordered_set({1, 6})); REQUIRE(groups.getDirectChildren(1) == std::unordered_set({})); REQUIRE(groups.getDirectChildren(2) == std::unordered_set({3, 8})); REQUIRE(groups.getDirectChildren(3) == std::unordered_set({0, 4})); REQUIRE(groups.getDirectChildren(4) == std::unordered_set({})); REQUIRE(groups.getDirectChildren(5) == std::unordered_set({})); REQUIRE(groups.getDirectChildren(6) == std::unordered_set({})); REQUIRE(groups.getDirectChildren(7) == std::unordered_set({})); REQUIRE(groups.getDirectChildren(8) == std::unordered_set({5, 7})); REQUIRE(groups.checkConsistency()); }; test_tree(); REQUIRE(groups.split(2, criterion, undo, redo)); auto test_tree2 = [&]() { REQUIRE(groups.getRootId(6) == 3); REQUIRE(groups.getRootId(3) == 3); REQUIRE(groups.getRootId(4) == 3); REQUIRE(groups.getSubtree(3) == std::unordered_set({3, 4, 6})); REQUIRE(groups.getDirectChildren(6) == std::unordered_set({})); REQUIRE(groups.getDirectChildren(4) == std::unordered_set({})); REQUIRE(groups.getDirectChildren(3) == std::unordered_set({6, 4})); // new tree int newRoot = groups.getRootId(1); REQUIRE(groups.getRootId(1) == newRoot); REQUIRE(groups.getRootId(5) == newRoot); REQUIRE(groups.getRootId(7) == newRoot); int other = -1; REQUIRE(groups.getDirectChildren(newRoot).size() == 2); for (int c : groups.getDirectChildren(newRoot)) if (c != 1) other = c; REQUIRE(other != -1); REQUIRE(groups.getSubtree(newRoot) == std::unordered_set({1, 5, 7, newRoot, other})); REQUIRE(groups.getDirectChildren(1) == std::unordered_set({})); REQUIRE(groups.getDirectChildren(5) == std::unordered_set({})); REQUIRE(groups.getDirectChildren(7) == std::unordered_set({})); REQUIRE(groups.getDirectChildren(newRoot) == std::unordered_set({1, other})); REQUIRE(groups.getDirectChildren(other) == std::unordered_set({5, 7})); REQUIRE(groups.checkConsistency()); }; test_tree2(); undo(); test_tree(); redo(); test_tree2(); } SECTION("Splitting preserves group type") { Fun undo = []() { return true; }; Fun redo = []() { return true; }; REQUIRE(groups.m_upLink.size() == 0); // This is a dummy split criterion auto criterion = [](int a) { return a % 2 == 0; }; // We create a very simple tree for (int i = 0; i <= 6; i++) { groups.createGroupItem(i); } groups.setGroup(0, 4); groups.setGroup(2, 4); groups.setGroup(1, 5); groups.setGroup(3, 5); groups.setGroup(4, 6); groups.setGroup(5, 6); groups.setType(4, GroupType::AVSplit); groups.setType(5, GroupType::AVSplit); groups.setType(6, GroupType::Normal); auto test_tree = [&]() { REQUIRE(groups.m_upLink.size() == 7); for (int i = 0; i <= 6; i++) { REQUIRE(groups.getRootId(i) == 6); } REQUIRE(groups.getSubtree(6) == std::unordered_set({0, 1, 2, 3, 4, 5, 6})); REQUIRE(groups.getDirectChildren(0) == std::unordered_set({})); REQUIRE(groups.getDirectChildren(1) == std::unordered_set({})); REQUIRE(groups.getDirectChildren(2) == std::unordered_set({})); REQUIRE(groups.getDirectChildren(3) == std::unordered_set({})); REQUIRE(groups.getDirectChildren(4) == std::unordered_set({0, 2})); REQUIRE(groups.getDirectChildren(5) == std::unordered_set({1, 3})); REQUIRE(groups.getDirectChildren(6) == std::unordered_set({4, 5})); REQUIRE(groups.getType(4) == GroupType::AVSplit); REQUIRE(groups.getType(5) == GroupType::AVSplit); REQUIRE(groups.getType(6) == GroupType::Normal); REQUIRE(groups.checkConsistency()); }; test_tree(); qDebug() << " done testing"; REQUIRE(groups.split(6, criterion, undo, redo)); qDebug() << " done splitting"; auto test_tree2 = [&]() { // REQUIRE(groups.m_upLink.size() == 6); int r1 = groups.getRootId(0); int r2 = groups.getRootId(1); bool ok = r1 == 4 || r2 == 5; REQUIRE(ok); REQUIRE(groups.getRootId(2) == r1); REQUIRE(groups.getRootId(3) == r2); REQUIRE(groups.getSubtree(r1) == std::unordered_set({r1, 0, 2})); REQUIRE(groups.getSubtree(r2) == std::unordered_set({r2, 1, 3})); REQUIRE(groups.getDirectChildren(0) == std::unordered_set({})); REQUIRE(groups.getDirectChildren(1) == std::unordered_set({})); REQUIRE(groups.getDirectChildren(2) == std::unordered_set({})); REQUIRE(groups.getDirectChildren(3) == std::unordered_set({})); REQUIRE(groups.getDirectChildren(r1) == std::unordered_set({0, 2})); REQUIRE(groups.getDirectChildren(r2) == std::unordered_set({1, 3})); REQUIRE(groups.getType(r1) == GroupType::AVSplit); REQUIRE(groups.getType(r2) == GroupType::AVSplit); REQUIRE(groups.checkConsistency()); }; test_tree2(); undo(); test_tree(); redo(); test_tree2(); undo(); test_tree(); redo(); test_tree2(); } binModel->clean(); pCore->m_projectManager = nullptr; } diff --git a/tests/keyframetest.cpp b/tests/keyframetest.cpp index fceebb6b2..c13c60d83 100644 --- a/tests/keyframetest.cpp +++ b/tests/keyframetest.cpp @@ -1,250 +1,252 @@ #include #include "test_utils.hpp" using namespace fakeit; bool test_model_equality(const std::shared_ptr &m1, const std::shared_ptr &m2) { // we cheat a bit by simply comparing the underlying map qDebug() << "Equality test" << m1->m_keyframeList.size() << m2->m_keyframeList.size(); QList model1; QList model2; for (const auto &m : m1->m_keyframeList) { model1 << m.first.frames(25) << (int)m.second.first << m.second.second; } for (const auto &m : m2->m_keyframeList) { model2 << m.first.frames(25) << (int)m.second.first << m.second.second; } return model1 == model2; } bool check_anim_identity(const std::shared_ptr &m) { auto m2 = std::make_shared(m->m_model, m->m_index, m->m_undoStack); m2->parseAnimProperty(m->getAnimProperty()); return test_model_equality(m, m2); } TEST_CASE("Keyframe model", "[KeyframeModel]") { + Logger::clear(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); // Here we do some trickery to enable testing. // We mock the project class so that the undoStack function returns our undoStack Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; Mlt::Profile pr; std::shared_ptr producer = std::make_shared(pr, "color", "red"); auto effectstack = EffectStackModel::construct(producer, {ObjectType::TimelineClip, 0}, undoStack); effectstack->appendEffect(QStringLiteral("audiobalance")); REQUIRE(effectstack->checkConsistency()); REQUIRE(effectstack->rowCount() == 1); auto effect = std::dynamic_pointer_cast(effectstack->getEffectStackRow(0)); effect->prepareKeyframes(); qDebug() << effect->getAssetId() << effect->getAllParameters(); REQUIRE(effect->rowCount() == 1); QModelIndex index = effect->index(0, 0); auto model = std::make_shared(effect, index, undoStack); SECTION("Add/remove + undo") { auto state0 = [&]() { REQUIRE(model->rowCount() == 1); REQUIRE(check_anim_identity(model)); }; state0(); REQUIRE(model->addKeyframe(GenTime(1.1), KeyframeType::Linear, 42)); auto state1 = [&]() { REQUIRE(model->rowCount() == 2); REQUIRE(check_anim_identity(model)); REQUIRE(model->hasKeyframe(GenTime(1.1))); bool ok; auto k = model->getKeyframe(GenTime(1.1), &ok); REQUIRE(ok); auto k0 = model->getKeyframe(GenTime(0), &ok); REQUIRE(ok); auto k1 = model->getClosestKeyframe(GenTime(0.655555), &ok); REQUIRE(ok); REQUIRE(k1 == k); auto k2 = model->getNextKeyframe(GenTime(0.5), &ok); REQUIRE(ok); REQUIRE(k2 == k); auto k3 = model->getPrevKeyframe(GenTime(0.5), &ok); REQUIRE(ok); REQUIRE(k3 == k0); auto k4 = model->getPrevKeyframe(GenTime(10), &ok); REQUIRE(ok); REQUIRE(k4 == k); model->getNextKeyframe(GenTime(10), &ok); REQUIRE_FALSE(ok); }; state1(); undoStack->undo(); state0(); undoStack->redo(); state1(); REQUIRE(model->addKeyframe(GenTime(12.6), KeyframeType::Discrete, 33)); auto state2 = [&]() { REQUIRE(model->rowCount() == 3); REQUIRE(check_anim_identity(model)); REQUIRE(model->hasKeyframe(GenTime(1.1))); REQUIRE(model->hasKeyframe(GenTime(12.6))); bool ok; auto k = model->getKeyframe(GenTime(1.1), &ok); REQUIRE(ok); auto k0 = model->getKeyframe(GenTime(0), &ok); REQUIRE(ok); auto kk = model->getKeyframe(GenTime(12.6), &ok); REQUIRE(ok); auto k1 = model->getClosestKeyframe(GenTime(0.655555), &ok); REQUIRE(ok); REQUIRE(k1 == k); auto k2 = model->getNextKeyframe(GenTime(0.5), &ok); REQUIRE(ok); REQUIRE(k2 == k); auto k3 = model->getPrevKeyframe(GenTime(0.5), &ok); REQUIRE(ok); REQUIRE(k3 == k0); auto k4 = model->getPrevKeyframe(GenTime(10), &ok); REQUIRE(ok); REQUIRE(k4 == k); auto k5 = model->getNextKeyframe(GenTime(10), &ok); REQUIRE(ok); REQUIRE(k5 == kk); }; state2(); undoStack->undo(); state1(); undoStack->undo(); state0(); undoStack->redo(); state1(); undoStack->redo(); state2(); REQUIRE(model->removeKeyframe(GenTime(1.1))); auto state3 = [&]() { REQUIRE(model->rowCount() == 2); REQUIRE(check_anim_identity(model)); REQUIRE(model->hasKeyframe(GenTime(12.6))); bool ok; model->getKeyframe(GenTime(1.1), &ok); REQUIRE_FALSE(ok); auto k0 = model->getKeyframe(GenTime(0), &ok); REQUIRE(ok); auto kk = model->getKeyframe(GenTime(12.6), &ok); REQUIRE(ok); auto k1 = model->getClosestKeyframe(GenTime(0.655555), &ok); REQUIRE(ok); REQUIRE(k1 == k0); auto k2 = model->getNextKeyframe(GenTime(0.5), &ok); REQUIRE(ok); REQUIRE(k2 == kk); auto k3 = model->getPrevKeyframe(GenTime(0.5), &ok); REQUIRE(ok); REQUIRE(k3 == k0); auto k4 = model->getPrevKeyframe(GenTime(10), &ok); REQUIRE(ok); REQUIRE(k4 == k0); auto k5 = model->getNextKeyframe(GenTime(10), &ok); REQUIRE(ok); REQUIRE(k5 == kk); }; state3(); undoStack->undo(); state2(); undoStack->undo(); state1(); undoStack->undo(); state0(); undoStack->redo(); state1(); undoStack->redo(); state2(); undoStack->redo(); state3(); REQUIRE(model->removeAllKeyframes()); state0(); undoStack->undo(); state3(); undoStack->redo(); state0(); } SECTION("Move keyframes + undo") { auto state0 = [&]() { REQUIRE(model->rowCount() == 1); REQUIRE(check_anim_identity(model)); }; state0(); REQUIRE(model->addKeyframe(GenTime(1.1), KeyframeType::Linear, 42)); auto state1 = [&](double pos) { REQUIRE(model->rowCount() == 2); REQUIRE(check_anim_identity(model)); REQUIRE(model->hasKeyframe(GenTime(pos))); bool ok; auto k = model->getKeyframe(GenTime(pos), &ok); REQUIRE(ok); auto k0 = model->getKeyframe(GenTime(0), &ok); REQUIRE(ok); auto k1 = model->getClosestKeyframe(GenTime(pos + 10), &ok); REQUIRE(ok); REQUIRE(k1 == k); auto k2 = model->getNextKeyframe(GenTime(pos - 0.3), &ok); REQUIRE(ok); REQUIRE(k2 == k); auto k3 = model->getPrevKeyframe(GenTime(pos - 0.3), &ok); REQUIRE(ok); REQUIRE(k3 == k0); auto k4 = model->getPrevKeyframe(GenTime(pos + 0.3), &ok); REQUIRE(ok); REQUIRE(k4 == k); model->getNextKeyframe(GenTime(pos + 0.3), &ok); REQUIRE_FALSE(ok); }; state1(1.1); REQUIRE(model->moveKeyframe(GenTime(1.1), GenTime(2.6), -1, true)); state1(2.6); undoStack->undo(); state1(1.1); undoStack->redo(); state1(2.6); REQUIRE(model->moveKeyframe(GenTime(2.6), GenTime(6.1), -1, true)); state1(6.1); undoStack->undo(); state1(2.6); undoStack->undo(); state1(1.1); undoStack->redo(); state1(2.6); undoStack->redo(); state1(6.1); REQUIRE(model->addKeyframe(GenTime(12.6), KeyframeType::Discrete, 33)); REQUIRE_FALSE(model->moveKeyframe(GenTime(6.1), GenTime(12.6), -1, true)); undoStack->undo(); state1(6.1); } pCore->m_projectManager = nullptr; + Logger::print_trace(); } diff --git a/tests/modeltest.cpp b/tests/modeltest.cpp index 987b01470..0013e9e38 100644 --- a/tests/modeltest.cpp +++ b/tests/modeltest.cpp @@ -1,2191 +1,2190 @@ -#include "logger.hpp" #include "test_utils.hpp" using namespace fakeit; std::default_random_engine g(42); Mlt::Profile profile_model; TEST_CASE("Basic creation/deletion of a track", "[TrackModel]") { Logger::clear(); auto binModel = pCore->projectItemModel(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); // Here we do some trickery to enable testing. // We mock the project class so that the undoStack function returns our undoStack Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; // We also mock timeline object to spy few functions and mock others TimelineItemModel tim(&profile_model, undoStack); Mock timMock(tim); auto timeline = std::shared_ptr(&timMock.get(), [](...) {}); TimelineItemModel::finishConstruct(timeline, guideModel); Fake(Method(timMock, adjustAssetRange)); // This is faked to allow to count calls Fake(Method(timMock, _resetView)); int id1 = TrackModel::construct(timeline); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTracksCount() == 1); REQUIRE(timeline->getTrackPosition(id1) == 0); // In the current implementation, when a track is added/removed, the model is notified with _resetView Verify(Method(timMock, _resetView)).Exactly(Once); RESET(timMock); int id2 = TrackModel::construct(timeline); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTracksCount() == 2); REQUIRE(timeline->getTrackPosition(id2) == 1); Verify(Method(timMock, _resetView)).Exactly(Once); RESET(timMock); int id3 = TrackModel::construct(timeline); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTracksCount() == 3); REQUIRE(timeline->getTrackPosition(id3) == 2); Verify(Method(timMock, _resetView)).Exactly(Once); RESET(timMock); int id4; REQUIRE(timeline->requestTrackInsertion(1, id4)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTracksCount() == 4); REQUIRE(timeline->getTrackPosition(id1) == 0); REQUIRE(timeline->getTrackPosition(id4) == 1); REQUIRE(timeline->getTrackPosition(id2) == 2); REQUIRE(timeline->getTrackPosition(id3) == 3); Verify(Method(timMock, _resetView)).Exactly(Once); RESET(timMock); // Test deletion REQUIRE(timeline->requestTrackDeletion(id3)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTracksCount() == 3); Verify(Method(timMock, _resetView)).Exactly(Once); RESET(timMock); REQUIRE(timeline->requestTrackDeletion(id1)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTracksCount() == 2); Verify(Method(timMock, _resetView)).Exactly(Once); RESET(timMock); REQUIRE(timeline->requestTrackDeletion(id4)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTracksCount() == 1); Verify(Method(timMock, _resetView)).Exactly(Once); RESET(timMock); REQUIRE(timeline->requestTrackDeletion(id2)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTracksCount() == 0); Verify(Method(timMock, _resetView)).Exactly(Once); RESET(timMock); SECTION("Delete a track with groups") { int tid1, tid2; REQUIRE(timeline->requestTrackInsertion(-1, tid1)); REQUIRE(timeline->requestTrackInsertion(-1, tid2)); REQUIRE(timeline->checkConsistency()); QString binId = createProducer(profile_model, "red", binModel); int length = 20; int cid1, cid2, cid3, cid4; REQUIRE(timeline->requestClipInsertion(binId, tid1, 2, cid1)); REQUIRE(timeline->requestClipInsertion(binId, tid2, 0, cid2)); REQUIRE(timeline->requestClipInsertion(binId, tid2, length, cid3)); REQUIRE(timeline->requestClipInsertion(binId, tid2, 2 * length, cid4)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipsCount() == 4); REQUIRE(timeline->getTracksCount() == 2); auto g1 = std::unordered_set({cid1, cid3}); auto g2 = std::unordered_set({cid2, cid4}); auto g3 = std::unordered_set({cid1, cid4}); REQUIRE(timeline->requestClipsGroup(g1)); REQUIRE(timeline->requestClipsGroup(g2)); REQUIRE(timeline->requestClipsGroup(g3)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->requestTrackDeletion(tid1)); REQUIRE(timeline->getClipsCount() == 3); REQUIRE(timeline->getTracksCount() == 1); REQUIRE(timeline->checkConsistency()); } binModel->clean(); pCore->m_projectManager = nullptr; Logger::print_trace(); } TEST_CASE("Basic creation/deletion of a clip", "[ClipModel]") { Logger::clear(); auto binModel = pCore->projectItemModel(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); std::shared_ptr timeline = TimelineItemModel::construct(&profile_model, guideModel, undoStack); // Here we do some trickery to enable testing. // We mock the project class so that the undoStack function returns our undoStack Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; QString binId = createProducer(profile_model, "red", binModel); QString binId2 = createProducer(profile_model, "green", binModel); REQUIRE(timeline->getClipsCount() == 0); int id1 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly); REQUIRE(timeline->getClipsCount() == 1); REQUIRE(timeline->checkConsistency()); int id2 = ClipModel::construct(timeline, binId2, -1, PlaylistState::VideoOnly); REQUIRE(timeline->getClipsCount() == 2); REQUIRE(timeline->checkConsistency()); int id3 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly); REQUIRE(timeline->getClipsCount() == 3); REQUIRE(timeline->checkConsistency()); // Test deletion REQUIRE(timeline->requestItemDeletion(id2)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipsCount() == 2); REQUIRE(timeline->requestItemDeletion(id3)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipsCount() == 1); REQUIRE(timeline->requestItemDeletion(id1)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipsCount() == 0); binModel->clean(); pCore->m_projectManager = nullptr; Logger::print_trace(); } TEST_CASE("Clip manipulation", "[ClipModel]") { Logger::clear(); auto binModel = pCore->projectItemModel(); binModel->clean(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); // Here we do some trickery to enable testing. // We mock the project class so that the undoStack function returns our undoStack Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; // We also mock timeline object to spy few functions and mock others TimelineItemModel tim(&profile_model, undoStack); Mock timMock(tim); auto timeline = std::shared_ptr(&timMock.get(), [](...) {}); TimelineItemModel::finishConstruct(timeline, guideModel); Fake(Method(timMock, adjustAssetRange)); // This is faked to allow to count calls Fake(Method(timMock, _resetView)); Fake(Method(timMock, _beginInsertRows)); Fake(Method(timMock, _beginRemoveRows)); Fake(Method(timMock, _endInsertRows)); Fake(Method(timMock, _endRemoveRows)); QString binId = createProducer(profile_model, "red", binModel); QString binId2 = createProducer(profile_model, "blue", binModel); QString binId3 = createProducer(profile_model, "green", binModel); int cid1 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly); int tid1 = TrackModel::construct(timeline); int tid2 = TrackModel::construct(timeline); int tid3 = TrackModel::construct(timeline); int cid2 = ClipModel::construct(timeline, binId2, -1, PlaylistState::VideoOnly); int cid3 = ClipModel::construct(timeline, binId3, -1, PlaylistState::VideoOnly); int cid4 = ClipModel::construct(timeline, binId2, -1, PlaylistState::VideoOnly); Verify(Method(timMock, _resetView)).Exactly(3_Times); RESET(timMock); // for testing purposes, we make sure the clip will behave as regular clips // (ie their size is fixed, we cannot resize them past their original size) timeline->m_allClips[cid1]->m_endlessResize = false; timeline->m_allClips[cid2]->m_endlessResize = false; timeline->m_allClips[cid3]->m_endlessResize = false; timeline->m_allClips[cid4]->m_endlessResize = false; SECTION("Insert a clip in a track and change track") { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 0); REQUIRE(timeline->getTrackClipsCount(tid2) == 0); REQUIRE(timeline->getClipTrackId(cid1) == -1); REQUIRE(timeline->getClipPosition(cid1) == -1); int pos = 10; REQUIRE(timeline->requestClipMove(cid1, tid1, pos)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipPosition(cid1) == pos); REQUIRE(timeline->getTrackClipsCount(tid1) == 1); REQUIRE(timeline->getTrackClipsCount(tid2) == 0); // Check that the model was correctly notified CHECK_INSERT(Once); pos = 1; REQUIRE(timeline->requestClipMove(cid1, tid2, pos)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipTrackId(cid1) == tid2); REQUIRE(timeline->getClipPosition(cid1) == pos); REQUIRE(timeline->getTrackClipsCount(tid2) == 1); REQUIRE(timeline->getTrackClipsCount(tid1) == 0); CHECK_MOVE(Once); // Check conflicts int pos2 = binModel->getClipByBinID(binId)->frameDuration(); REQUIRE(timeline->requestClipMove(cid2, tid1, pos2)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipTrackId(cid2) == tid1); REQUIRE(timeline->getClipPosition(cid2) == pos2); REQUIRE(timeline->getTrackClipsCount(tid2) == 1); REQUIRE(timeline->getTrackClipsCount(tid1) == 1); CHECK_INSERT(Once); REQUIRE_FALSE(timeline->requestClipMove(cid1, tid1, pos2 + 2)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid2) == 1); REQUIRE(timeline->getTrackClipsCount(tid1) == 1); REQUIRE(timeline->getClipTrackId(cid1) == tid2); REQUIRE(timeline->getClipPosition(cid1) == pos); REQUIRE(timeline->getClipTrackId(cid2) == tid1); REQUIRE(timeline->getClipPosition(cid2) == pos2); CHECK_MOVE(Once); REQUIRE_FALSE(timeline->requestClipMove(cid1, tid1, pos2 - 2)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid2) == 1); REQUIRE(timeline->getTrackClipsCount(tid1) == 1); REQUIRE(timeline->getClipTrackId(cid1) == tid2); REQUIRE(timeline->getClipPosition(cid1) == pos); REQUIRE(timeline->getClipTrackId(cid2) == tid1); REQUIRE(timeline->getClipPosition(cid2) == pos2); CHECK_MOVE(Once); REQUIRE(timeline->requestClipMove(cid1, tid1, 0)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid2) == 0); REQUIRE(timeline->getTrackClipsCount(tid1) == 2); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipPosition(cid1) == 0); REQUIRE(timeline->getClipTrackId(cid2) == tid1); REQUIRE(timeline->getClipPosition(cid2) == pos2); CHECK_MOVE(Once); } int length = binModel->getClipByBinID(binId)->frameDuration(); SECTION("Insert consecutive clips") { REQUIRE(timeline->requestClipMove(cid1, tid1, 0)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipPosition(cid1) == 0); REQUIRE(timeline->getTrackClipsCount(tid1) == 1); CHECK_INSERT(Once); REQUIRE(timeline->requestClipMove(cid2, tid1, length)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipTrackId(cid2) == tid1); REQUIRE(timeline->getClipPosition(cid2) == length); REQUIRE(timeline->getTrackClipsCount(tid1) == 2); CHECK_INSERT(Once); } SECTION("Resize orphan clip") { REQUIRE(timeline->getClipPlaytime(cid2) == length); REQUIRE(timeline->requestItemResize(cid2, 5, true) == 5); REQUIRE(timeline->checkConsistency()); REQUIRE(binModel->getClipByBinID(binId)->frameDuration() == length); auto inOut = std::pair{0, 4}; REQUIRE(timeline->m_allClips[cid2]->getInOut() == inOut); REQUIRE(timeline->getClipPlaytime(cid2) == 5); REQUIRE(timeline->requestItemResize(cid2, 10, false) == -1); REQUIRE(timeline->requestItemResize(cid2, length + 1, true) == -1); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipPlaytime(cid2) == 5); REQUIRE(timeline->getClipPlaytime(cid2) == 5); REQUIRE(timeline->requestItemResize(cid2, 2, false) == 2); REQUIRE(timeline->checkConsistency()); inOut = std::pair{3, 4}; REQUIRE(timeline->m_allClips[cid2]->getInOut() == inOut); REQUIRE(timeline->getClipPlaytime(cid2) == 2); REQUIRE(timeline->requestItemResize(cid2, length, true) == -1); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipPlaytime(cid2) == 2); CAPTURE(timeline->m_allClips[cid2]->m_producer->get_in()); REQUIRE(timeline->requestItemResize(cid2, length - 2, true) == -1); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->requestItemResize(cid2, length - 3, true) == length - 3); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipPlaytime(cid2) == length - 3); } SECTION("Resize inserted clips") { REQUIRE(timeline->requestClipMove(cid1, tid1, 0)); REQUIRE(timeline->checkConsistency()); CHECK_INSERT(Once); REQUIRE(timeline->requestItemResize(cid1, 5, true) == 5); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipPlaytime(cid1) == 5); REQUIRE(timeline->getClipPosition(cid1) == 0); CHECK_RESIZE(Once); REQUIRE(timeline->requestClipMove(cid2, tid1, 5)); REQUIRE(timeline->checkConsistency()); REQUIRE(binModel->getClipByBinID(binId)->frameDuration() == length); CHECK_INSERT(Once); REQUIRE(timeline->requestItemResize(cid1, 6, true) == -1); REQUIRE(timeline->requestItemResize(cid1, 6, false) == -1); REQUIRE(timeline->checkConsistency()); NO_OTHERS(); REQUIRE(timeline->requestItemResize(cid2, length - 5, false) == length - 5); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipPosition(cid2) == 10); CHECK_RESIZE(Once); REQUIRE(timeline->requestItemResize(cid1, 10, true) == 10); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 2); CHECK_RESIZE(Once); } SECTION("Change track of resized clips") { // // REQUIRE(timeline->allowClipMove(cid2, tid1, 5)); REQUIRE(timeline->requestClipMove(cid2, tid1, 5)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 1); // // REQUIRE(timeline->allowClipMove(cid1, tid2, 10)); REQUIRE(timeline->requestClipMove(cid1, tid2, 10)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid2) == 1); REQUIRE(timeline->requestItemResize(cid1, 5, false) == 5); REQUIRE(timeline->checkConsistency()); // // REQUIRE(timeline->allowClipMove(cid1, tid1, 0)); REQUIRE(timeline->requestClipMove(cid1, tid1, 0)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 2); REQUIRE(timeline->getTrackClipsCount(tid2) == 0); } SECTION("Clip Move") { REQUIRE(timeline->requestClipMove(cid2, tid1, 5)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 1); REQUIRE(timeline->getClipTrackId(cid2) == tid1); REQUIRE(timeline->getClipPosition(cid2) == 5); REQUIRE(timeline->requestClipMove(cid1, tid1, 5 + length)); auto state = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 2); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipTrackId(cid2) == tid1); REQUIRE(timeline->getClipPosition(cid1) == 5 + length); REQUIRE(timeline->getClipPosition(cid2) == 5); }; state(); REQUIRE_FALSE(timeline->requestClipMove(cid1, tid1, 3 + length)); state(); REQUIRE_FALSE(timeline->requestClipMove(cid1, tid1, 0)); state(); REQUIRE(timeline->requestClipMove(cid2, tid1, 0)); auto state2 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 2); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipTrackId(cid2) == tid1); REQUIRE(timeline->getClipPosition(cid1) == 5 + length); REQUIRE(timeline->getClipPosition(cid2) == 0); }; state2(); REQUIRE_FALSE(timeline->requestClipMove(cid1, tid1, 0)); state2(); REQUIRE_FALSE(timeline->requestClipMove(cid1, tid1, length - 5)); state2(); REQUIRE(timeline->requestClipMove(cid1, tid1, length)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 2); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipTrackId(cid2) == tid1); REQUIRE(timeline->getClipPosition(cid1) == length); REQUIRE(timeline->getClipPosition(cid2) == 0); REQUIRE(timeline->requestItemResize(cid2, length - 5, true) == length - 5); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipTrackId(cid2) == tid1); REQUIRE(timeline->getClipPosition(cid1) == length); REQUIRE(timeline->getClipPosition(cid2) == 0); REQUIRE(timeline->requestClipMove(cid1, tid1, length - 5)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 2); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipTrackId(cid2) == tid1); REQUIRE(timeline->getClipPosition(cid1) == length - 5); REQUIRE(timeline->getClipPosition(cid2) == 0); REQUIRE(timeline->requestItemResize(cid2, length - 10, false) == length - 10); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipTrackId(cid2) == tid1); REQUIRE(timeline->getClipPosition(cid1) == length - 5); REQUIRE(timeline->getClipPosition(cid2) == 5); REQUIRE_FALSE(timeline->requestClipMove(cid1, tid1, 0)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipTrackId(cid2) == tid1); REQUIRE(timeline->getClipPosition(cid1) == length - 5); REQUIRE(timeline->getClipPosition(cid2) == 5); REQUIRE(timeline->requestClipMove(cid2, tid1, 0)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 2); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipTrackId(cid2) == tid1); REQUIRE(timeline->getClipPosition(cid1) == length - 5); REQUIRE(timeline->getClipPosition(cid2) == 0); } SECTION("Move and resize") { REQUIRE(timeline->requestClipMove(cid1, tid1, 0)); REQUIRE(timeline->requestItemResize(cid1, length - 2, false) == length - 2); REQUIRE(timeline->requestClipMove(cid1, tid1, 0)); auto state = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getTrackClipsCount(tid1) == 1); REQUIRE(timeline->getClipPosition(cid1) == 0); REQUIRE(timeline->getClipPlaytime(cid1) == length - 2); }; state(); // try to resize past the left end REQUIRE(timeline->requestItemResize(cid1, length, false) == -1); state(); REQUIRE(timeline->requestItemResize(cid1, length - 4, true) == length - 4); REQUIRE(timeline->requestClipMove(cid2, tid1, length - 4 + 1)); REQUIRE(timeline->requestItemResize(cid2, length - 2, false) == length - 2); REQUIRE(timeline->requestClipMove(cid2, tid1, length - 4 + 1)); auto state2 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipTrackId(cid2) == tid1); REQUIRE(timeline->getTrackClipsCount(tid1) == 2); REQUIRE(timeline->getClipPosition(cid1) == 0); REQUIRE(timeline->getClipPlaytime(cid1) == length - 4); REQUIRE(timeline->getClipPosition(cid2) == length - 4 + 1); REQUIRE(timeline->getClipPlaytime(cid2) == length - 2); }; state2(); // the gap between the two clips is 1 frame, we try to resize them by 2 frames REQUIRE(timeline->requestItemResize(cid1, length - 2, true) == -1); state2(); REQUIRE(timeline->requestItemResize(cid2, length, false) == -1); state2(); REQUIRE(timeline->requestClipMove(cid2, tid1, length - 4)); auto state3 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipTrackId(cid2) == tid1); REQUIRE(timeline->getTrackClipsCount(tid1) == 2); REQUIRE(timeline->getClipPosition(cid1) == 0); REQUIRE(timeline->getClipPlaytime(cid1) == length - 4); REQUIRE(timeline->getClipPosition(cid2) == length - 4); REQUIRE(timeline->getClipPlaytime(cid2) == length - 2); }; state3(); // Now the gap is 0 frames, the resize should still fail REQUIRE(timeline->requestItemResize(cid1, length - 2, true) == -1); state3(); REQUIRE(timeline->requestItemResize(cid2, length, false) == -1); state3(); // We move cid1 out of the way REQUIRE(timeline->requestClipMove(cid1, tid2, 0)); // now resize should work REQUIRE(timeline->requestItemResize(cid1, length - 2, true) == length - 2); REQUIRE(timeline->requestItemResize(cid2, length, false) == length); REQUIRE(timeline->checkConsistency()); } SECTION("Group move") { REQUIRE(timeline->requestClipMove(cid1, tid1, 0)); REQUIRE(timeline->requestClipMove(cid2, tid1, length + 3)); REQUIRE(timeline->requestClipMove(cid3, tid1, 2 * length + 5)); REQUIRE(timeline->requestClipMove(cid4, tid2, 4)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 3); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipTrackId(cid2) == tid1); REQUIRE(timeline->getClipTrackId(cid3) == tid1); REQUIRE(timeline->getClipTrackId(cid4) == tid2); REQUIRE(timeline->getClipPosition(cid1) == 0); REQUIRE(timeline->getClipPosition(cid2) == length + 3); REQUIRE(timeline->getClipPosition(cid3) == 2 * length + 5); REQUIRE(timeline->getClipPosition(cid4) == 4); // check that move is possible without groups REQUIRE(timeline->requestClipMove(cid3, tid1, 2 * length + 3)); REQUIRE(timeline->checkConsistency()); undoStack->undo(); REQUIRE(timeline->checkConsistency()); // check that move is possible without groups REQUIRE(timeline->requestClipMove(cid4, tid2, 9)); REQUIRE(timeline->checkConsistency()); undoStack->undo(); REQUIRE(timeline->checkConsistency()); auto state = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 3); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipTrackId(cid2) == tid1); REQUIRE(timeline->getClipTrackId(cid3) == tid1); REQUIRE(timeline->getClipTrackId(cid4) == tid2); REQUIRE(timeline->getClipPosition(cid1) == 0); REQUIRE(timeline->getClipPosition(cid2) == length + 3); REQUIRE(timeline->getClipPosition(cid3) == 2 * length + 5); REQUIRE(timeline->getClipPosition(cid4) == 4); }; state(); // grouping REQUIRE(timeline->requestClipsGroup({cid1, cid3})); REQUIRE(timeline->requestClipsGroup({cid1, cid4})); // move left is now forbidden, because clip1 is at position 0 REQUIRE_FALSE(timeline->requestClipMove(cid3, tid1, 2 * length + 3)); state(); // this move is impossible, because clip1 runs into clip2 REQUIRE_FALSE(timeline->requestClipMove(cid4, tid2, 9)); state(); // this move is possible REQUIRE(timeline->requestClipMove(cid3, tid1, 2 * length + 8)); auto state1 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 3); REQUIRE(timeline->getTrackClipsCount(tid2) == 1); REQUIRE(timeline->getTrackClipsCount(tid3) == 0); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipTrackId(cid2) == tid1); REQUIRE(timeline->getClipTrackId(cid3) == tid1); REQUIRE(timeline->getClipTrackId(cid4) == tid2); REQUIRE(timeline->getClipPosition(cid1) == 3); REQUIRE(timeline->getClipPosition(cid2) == length + 3); REQUIRE(timeline->getClipPosition(cid3) == 2 * length + 8); REQUIRE(timeline->getClipPosition(cid4) == 7); }; state1(); // this move is possible REQUIRE(timeline->requestClipMove(cid1, tid2, 8)); auto state2 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 1); REQUIRE(timeline->getTrackClipsCount(tid2) == 2); REQUIRE(timeline->getTrackClipsCount(tid3) == 1); REQUIRE(timeline->getClipTrackId(cid1) == tid2); REQUIRE(timeline->getClipTrackId(cid2) == tid1); REQUIRE(timeline->getClipTrackId(cid3) == tid2); REQUIRE(timeline->getClipTrackId(cid4) == tid3); REQUIRE(timeline->getClipPosition(cid1) == 8); REQUIRE(timeline->getClipPosition(cid2) == length + 3); REQUIRE(timeline->getClipPosition(cid3) == 2 * length + 5 + 8); REQUIRE(timeline->getClipPosition(cid4) == 4 + 8); }; state2(); undoStack->undo(); state1(); undoStack->redo(); state2(); REQUIRE(timeline->requestClipMove(cid1, tid1, 3)); state1(); } SECTION("Group move consecutive clips") { REQUIRE(timeline->requestClipMove(cid1, tid1, 7)); REQUIRE(timeline->requestClipMove(cid2, tid1, 7 + length)); REQUIRE(timeline->requestClipMove(cid3, tid1, 7 + 2 * length)); REQUIRE(timeline->requestClipMove(cid4, tid1, 7 + 3 * length)); REQUIRE(timeline->requestClipsGroup({cid1, cid2, cid3, cid4})); auto state = [&](int tid, int start) { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid) == 4); int i = 0; for (int cid : std::vector({cid1, cid2, cid3, cid4})) { REQUIRE(timeline->getClipTrackId(cid) == tid); REQUIRE(timeline->getClipPosition(cid) == start + i * length); REQUIRE(timeline->getClipPlaytime(cid) == length); i++; } }; state(tid1, 7); auto check_undo = [&](int target, int tid, int oldTid) { state(tid, target); undoStack->undo(); state(oldTid, 7); undoStack->redo(); state(tid, target); undoStack->undo(); state(oldTid, 7); }; REQUIRE(timeline->requestClipMove(cid1, tid1, 6)); qDebug() << "state1"; state(tid1, 6); undoStack->undo(); state(tid1, 7); undoStack->redo(); state(tid1, 6); REQUIRE(timeline->requestClipMove(cid1, tid1, 0)); qDebug() << "state2"; state(tid1, 0); undoStack->undo(); state(tid1, 6); undoStack->redo(); state(tid1, 0); undoStack->undo(); state(tid1, 6); undoStack->undo(); state(tid1, 7); REQUIRE(timeline->requestClipMove(cid3, tid1, 1 + 2 * length)); qDebug() << "state3"; check_undo(1, tid1, tid1); REQUIRE(timeline->requestClipMove(cid4, tid1, 4 + 3 * length)); qDebug() << "state4"; check_undo(4, tid1, tid1); REQUIRE(timeline->requestClipMove(cid4, tid1, 11 + 3 * length)); qDebug() << "state5"; check_undo(11, tid1, tid1); REQUIRE(timeline->requestClipMove(cid2, tid1, 13 + length)); qDebug() << "state6"; check_undo(13, tid1, tid1); REQUIRE(timeline->requestClipMove(cid1, tid1, 20)); qDebug() << "state7"; check_undo(20, tid1, tid1); REQUIRE(timeline->requestClipMove(cid4, tid1, 7 + 4 * length)); qDebug() << "state8"; check_undo(length + 7, tid1, tid1); REQUIRE(timeline->requestClipMove(cid2, tid1, 7 + 2 * length)); qDebug() << "state9"; check_undo(length + 7, tid1, tid1); REQUIRE(timeline->requestClipMove(cid1, tid1, 7 + length)); qDebug() << "state10"; check_undo(length + 7, tid1, tid1); REQUIRE(timeline->requestClipMove(cid2, tid2, 8 + length)); qDebug() << "state11"; check_undo(8, tid2, tid1); } SECTION("Group move to unavailable track") { REQUIRE(timeline->requestClipMove(cid1, tid1, 10)); REQUIRE(timeline->requestClipMove(cid2, tid2, 12)); REQUIRE(timeline->requestClipsGroup({cid1, cid2})); auto state = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 1); REQUIRE(timeline->getTrackClipsCount(tid2) == 1); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipTrackId(cid2) == tid2); REQUIRE(timeline->getClipPosition(cid1) == 10); REQUIRE(timeline->getClipPosition(cid2) == 12); }; state(); REQUIRE_FALSE(timeline->requestClipMove(cid2, tid1, 10)); state(); REQUIRE_FALSE(timeline->requestClipMove(cid2, tid1, 100)); state(); REQUIRE_FALSE(timeline->requestClipMove(cid1, tid3, 100)); state(); } SECTION("Group move with non-consecutive track ids") { int tid5 = TrackModel::construct(timeline); int cid6 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly); Q_UNUSED(cid6); int tid6 = TrackModel::construct(timeline); REQUIRE(tid5 + 1 != tid6); REQUIRE(timeline->requestClipMove(cid1, tid5, 10)); REQUIRE(timeline->requestClipMove(cid2, tid5, length + 10)); REQUIRE(timeline->requestClipsGroup({cid1, cid2})); auto state = [&](int t) { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(t) == 2); REQUIRE(timeline->getClipTrackId(cid1) == t); REQUIRE(timeline->getClipTrackId(cid2) == t); REQUIRE(timeline->getClipPosition(cid1) == 10); REQUIRE(timeline->getClipPosition(cid2) == 10 + length); }; state(tid5); REQUIRE(timeline->requestClipMove(cid1, tid6, 10)); state(tid6); } SECTION("Creation and movement of AV groups") { int tid6b = TrackModel::construct(timeline, -1, -1, QString(), true); int tid6 = TrackModel::construct(timeline, -1, -1, QString(), true); int tid5 = TrackModel::construct(timeline); int tid5b = TrackModel::construct(timeline); QString binId3 = createProducerWithSound(profile_model, binModel); int cid6 = -1; REQUIRE(timeline->requestClipInsertion(binId3, tid5, 3, cid6, true, true, false)); int cid7 = timeline->m_groups->getSplitPartner(cid6); auto check_group = [&]() { // we check that the av group was correctly created REQUIRE(timeline->getGroupElements(cid6) == std::unordered_set({cid6, cid7})); int g1 = timeline->m_groups->getDirectAncestor(cid6); REQUIRE(timeline->m_groups->getDirectChildren(g1) == std::unordered_set({cid6, cid7})); REQUIRE(timeline->m_groups->getType(g1) == GroupType::AVSplit); }; auto state = [&](int pos) { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid5) == 1); REQUIRE(timeline->getTrackClipsCount(tid6) == 1); REQUIRE(timeline->getClipTrackId(cid6) == tid5); REQUIRE(timeline->getClipTrackId(cid7) == tid6); REQUIRE(timeline->getClipPosition(cid6) == pos); REQUIRE(timeline->getClipPosition(cid7) == pos); REQUIRE(timeline->getClipPtr(cid6)->clipState() == PlaylistState::VideoOnly); REQUIRE(timeline->getClipPtr(cid7)->clipState() == PlaylistState::AudioOnly); check_group(); }; state(3); // simple translation on the right REQUIRE(timeline->requestClipMove(cid6, tid5, 10, true, true, true)); state(10); undoStack->undo(); state(3); undoStack->redo(); state(10); // simple translation on the left, moving the audio clip this time REQUIRE(timeline->requestClipMove(cid7, tid6, 1, true, true, true)); state(1); undoStack->undo(); state(10); undoStack->redo(); state(1); // change track, moving video REQUIRE(timeline->requestClipMove(cid6, tid5b, 7, true, true, true)); auto state2 = [&](int pos) { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid5b) == 1); REQUIRE(timeline->getTrackClipsCount(tid6b) == 1); REQUIRE(timeline->getClipTrackId(cid6) == tid5b); REQUIRE(timeline->getClipTrackId(cid7) == tid6b); REQUIRE(timeline->getClipPosition(cid6) == pos); REQUIRE(timeline->getClipPosition(cid7) == pos); REQUIRE(timeline->getClipPtr(cid6)->clipState() == PlaylistState::VideoOnly); REQUIRE(timeline->getClipPtr(cid7)->clipState() == PlaylistState::AudioOnly); check_group(); }; state2(7); undoStack->undo(); state(1); undoStack->redo(); state2(7); // change track, moving audio REQUIRE(timeline->requestClipMove(cid7, tid6b, 2, true, true, true)); state2(2); undoStack->undo(); state2(7); undoStack->redo(); state2(2); undoStack->undo(); undoStack->undo(); state(1); } SECTION("Clip copy") { int cid6 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly); int l = timeline->getClipPlaytime(cid6); REQUIRE(timeline->requestItemResize(cid6, l - 3, true, true, -1) == l - 3); REQUIRE(timeline->requestItemResize(cid6, l - 7, false, true, -1) == l - 7); int newId; std::function undo = []() { return true; }; std::function redo = []() { return true; }; REQUIRE(TimelineFunctions::copyClip(timeline, cid6, newId, PlaylistState::VideoOnly, undo, redo)); REQUIRE(timeline->m_allClips[cid6]->binId() == timeline->m_allClips[newId]->binId()); // TODO check effects } binModel->clean(); pCore->m_projectManager = nullptr; Logger::print_trace(); } TEST_CASE("Check id unicity", "[ClipModel]") { Logger::clear(); auto binModel = pCore->projectItemModel(); binModel->clean(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); // Here we do some trickery to enable testing. // We mock the project class so that the undoStack function returns our undoStack Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; // We also mock timeline object to spy few functions and mock others TimelineItemModel tim(&profile_model, undoStack); Mock timMock(tim); auto timeline = std::shared_ptr(&timMock.get(), [](...) {}); TimelineItemModel::finishConstruct(timeline, guideModel); RESET(timMock); QString binId = createProducer(profile_model, "red", binModel); std::vector track_ids; std::unordered_set all_ids; std::bernoulli_distribution coin(0.5); const int nbr = 20; for (int i = 0; i < nbr; i++) { if (coin(g)) { int tid = TrackModel::construct(timeline); REQUIRE(all_ids.count(tid) == 0); all_ids.insert(tid); track_ids.push_back(tid); REQUIRE(timeline->getTracksCount() == track_ids.size()); } else { int cid = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly); REQUIRE(all_ids.count(cid) == 0); all_ids.insert(cid); REQUIRE(timeline->getClipsCount() == all_ids.size() - track_ids.size()); } } REQUIRE(timeline->checkConsistency()); REQUIRE(all_ids.size() == nbr); REQUIRE(all_ids.size() != track_ids.size()); binModel->clean(); pCore->m_projectManager = nullptr; Logger::print_trace(); } TEST_CASE("Undo and Redo", "[ClipModel]") { Logger::clear(); auto binModel = pCore->projectItemModel(); binModel->clean(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); // Here we do some trickery to enable testing. // We mock the project class so that the undoStack function returns our undoStack Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; // We also mock timeline object to spy few functions and mock others TimelineItemModel tim(&profile_model, undoStack); Mock timMock(tim); auto timeline = std::shared_ptr(&timMock.get(), [](...) {}); TimelineItemModel::finishConstruct(timeline, guideModel); RESET(timMock); QString binId = createProducer(profile_model, "red", binModel); QString binId2 = createProducer(profile_model, "blue", binModel); int cid1 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly); int tid1 = TrackModel::construct(timeline); int tid2 = TrackModel::construct(timeline); int cid2 = ClipModel::construct(timeline, binId2, -1, PlaylistState::VideoOnly); timeline->m_allClips[cid1]->m_endlessResize = false; timeline->m_allClips[cid2]->m_endlessResize = false; int length = 20; int nclips = timeline->m_allClips.size(); SECTION("requestCreateClip") { // an invalid clip id shouldn't get created { int temp; Fun undo = []() { return true; }; Fun redo = []() { return true; }; REQUIRE_FALSE(timeline->requestClipCreation("impossible bin id", temp, PlaylistState::VideoOnly, 1., undo, redo)); } auto state0 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->m_allClips.size() == nclips); }; state0(); QString binId3 = createProducer(profile_model, "green", binModel); int cid3; { Fun undo = []() { return true; }; Fun redo = []() { return true; }; REQUIRE(timeline->requestClipCreation(binId3, cid3, PlaylistState::VideoOnly, 1., undo, redo)); pCore->pushUndo(undo, redo, QString()); } auto state1 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->m_allClips.size() == nclips + 1); REQUIRE(timeline->getClipPlaytime(cid3) == length); REQUIRE(timeline->getClipTrackId(cid3) == -1); }; state1(); QString binId4 = binId3 + "/1/10"; int cid4; { Fun undo = []() { return true; }; Fun redo = []() { return true; }; REQUIRE(timeline->requestClipCreation(binId4, cid4, PlaylistState::VideoOnly, 1., undo, redo)); pCore->pushUndo(undo, redo, QString()); } auto state2 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->m_allClips.size() == nclips + 2); REQUIRE(timeline->getClipPlaytime(cid4) == 10); REQUIRE(timeline->getClipTrackId(cid4) == -1); auto inOut = std::pair({1, 10}); REQUIRE(timeline->m_allClips.at(cid4)->getInOut() == inOut); REQUIRE(timeline->getClipPlaytime(cid3) == length); REQUIRE(timeline->getClipTrackId(cid3) == -1); }; state2(); undoStack->undo(); state1(); undoStack->undo(); state0(); undoStack->redo(); state1(); undoStack->redo(); state2(); } SECTION("requestInsertClip") { auto state0 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->m_allClips.size() == nclips); }; state0(); QString binId3 = createProducer(profile_model, "green", binModel); int cid3; REQUIRE(timeline->requestClipInsertion(binId3, tid1, 12, cid3, true)); auto state1 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->m_allClips.size() == nclips + 1); REQUIRE(timeline->getClipPlaytime(cid3) == length); REQUIRE(timeline->getClipTrackId(cid3) == tid1); REQUIRE(timeline->getClipPosition(cid3) == 12); }; state1(); QString binId4 = binId3 + "/1/10"; int cid4; REQUIRE(timeline->requestClipInsertion(binId4, tid2, 17, cid4, true)); auto state2 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->m_allClips.size() == nclips + 2); REQUIRE(timeline->getClipPlaytime(cid4) == 10); REQUIRE(timeline->getClipTrackId(cid4) == tid2); REQUIRE(timeline->getClipPosition(cid4) == 17); auto inOut = std::pair({1, 10}); REQUIRE(timeline->m_allClips.at(cid4)->getInOut() == inOut); REQUIRE(timeline->getClipPlaytime(cid3) == length); REQUIRE(timeline->getClipTrackId(cid3) == tid1); REQUIRE(timeline->getClipPosition(cid3) == 12); }; state2(); undoStack->undo(); state1(); undoStack->undo(); state0(); undoStack->redo(); state1(); undoStack->redo(); state2(); } int init_index = undoStack->index(); SECTION("Basic move undo") { REQUIRE(timeline->requestClipMove(cid1, tid1, 5)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 1); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipPosition(cid1) == 5); REQUIRE(undoStack->index() == init_index + 1); CHECK_INSERT(Once); REQUIRE(timeline->requestClipMove(cid1, tid1, 0)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 1); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipPosition(cid1) == 0); REQUIRE(undoStack->index() == init_index + 2); // Move on same track does not trigger insert/remove row CHECK_MOVE(0); undoStack->undo(); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 1); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipPosition(cid1) == 5); REQUIRE(undoStack->index() == init_index + 1); CHECK_MOVE(0); undoStack->redo(); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 1); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipPosition(cid1) == 0); REQUIRE(undoStack->index() == init_index + 2); CHECK_MOVE(0); undoStack->undo(); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 1); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipPosition(cid1) == 5); REQUIRE(undoStack->index() == init_index + 1); CHECK_MOVE(0); REQUIRE(timeline->requestClipMove(cid1, tid1, 2 * length)); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 1); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipPosition(cid1) == 2 * length); REQUIRE(undoStack->index() == init_index + 2); CHECK_MOVE(0); undoStack->undo(); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 1); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipPosition(cid1) == 5); REQUIRE(undoStack->index() == init_index + 1); CHECK_MOVE(0); undoStack->redo(); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 1); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipPosition(cid1) == 2 * length); REQUIRE(undoStack->index() == init_index + 2); CHECK_MOVE(0); undoStack->undo(); CHECK_MOVE(0); undoStack->undo(); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 0); REQUIRE(timeline->getClipTrackId(cid1) == -1); REQUIRE(undoStack->index() == init_index); CHECK_REMOVE(Once); } SECTION("Basic resize orphan clip undo") { REQUIRE(timeline->getClipPlaytime(cid2) == length); REQUIRE(timeline->requestItemResize(cid2, length - 5, true) == length - 5); REQUIRE(undoStack->index() == init_index + 1); REQUIRE(timeline->getClipPlaytime(cid2) == length - 5); REQUIRE(timeline->requestItemResize(cid2, length - 10, false) == length - 10); REQUIRE(undoStack->index() == init_index + 2); REQUIRE(timeline->getClipPlaytime(cid2) == length - 10); REQUIRE(timeline->requestItemResize(cid2, length, false) == -1); REQUIRE(undoStack->index() == init_index + 2); REQUIRE(timeline->getClipPlaytime(cid2) == length - 10); undoStack->undo(); REQUIRE(undoStack->index() == init_index + 1); REQUIRE(timeline->getClipPlaytime(cid2) == length - 5); undoStack->redo(); REQUIRE(undoStack->index() == init_index + 2); REQUIRE(timeline->getClipPlaytime(cid2) == length - 10); undoStack->undo(); REQUIRE(undoStack->index() == init_index + 1); REQUIRE(timeline->getClipPlaytime(cid2) == length - 5); undoStack->undo(); REQUIRE(undoStack->index() == init_index); REQUIRE(timeline->getClipPlaytime(cid2) == length); } SECTION("Basic resize inserted clip undo") { REQUIRE(timeline->getClipPlaytime(cid2) == length); auto check = [&](int pos, int l) { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 1); REQUIRE(timeline->getClipTrackId(cid2) == tid1); REQUIRE(timeline->getClipPlaytime(cid2) == l); REQUIRE(timeline->getClipPosition(cid2) == pos); }; REQUIRE(timeline->requestClipMove(cid2, tid1, 5)); INFO("Test 1"); check(5, length); REQUIRE(undoStack->index() == init_index + 1); REQUIRE(timeline->requestItemResize(cid2, length - 5, true) == length - 5); INFO("Test 2"); check(5, length - 5); REQUIRE(undoStack->index() == init_index + 2); REQUIRE(timeline->requestItemResize(cid2, length - 10, false) == length - 10); INFO("Test 3"); check(10, length - 10); REQUIRE(undoStack->index() == init_index + 3); REQUIRE(timeline->requestItemResize(cid2, length, false) == -1); INFO("Test 4"); check(10, length - 10); REQUIRE(undoStack->index() == init_index + 3); undoStack->undo(); INFO("Test 5"); check(5, length - 5); REQUIRE(undoStack->index() == init_index + 2); undoStack->redo(); INFO("Test 6"); check(10, length - 10); REQUIRE(undoStack->index() == init_index + 3); undoStack->undo(); INFO("Test 7"); check(5, length - 5); REQUIRE(undoStack->index() == init_index + 2); undoStack->undo(); INFO("Test 8"); check(5, length); REQUIRE(undoStack->index() == init_index + 1); } SECTION("Clip Insertion Undo") { QString binId3 = createProducer(profile_model, "red", binModel); REQUIRE(timeline->requestClipMove(cid1, tid1, 5)); auto state1 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 1); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipPosition(cid1) == 5); REQUIRE(undoStack->index() == init_index + 1); }; state1(); int cid3; REQUIRE_FALSE(timeline->requestClipInsertion(binId3, tid1, 5, cid3)); state1(); REQUIRE_FALSE(timeline->requestClipInsertion(binId3, tid1, 6, cid3)); state1(); REQUIRE(timeline->requestClipInsertion(binId3, tid1, 5 + length, cid3)); auto state2 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 2); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipTrackId(cid3) == tid1); REQUIRE(timeline->getClipPosition(cid1) == 5); REQUIRE(timeline->getClipPosition(cid3) == 5 + length); REQUIRE(timeline->m_allClips[cid3]->isValid()); REQUIRE(undoStack->index() == init_index + 2); }; state2(); REQUIRE(timeline->requestClipMove(cid3, tid1, 10 + length)); auto state3 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 2); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipTrackId(cid3) == tid1); REQUIRE(timeline->getClipPosition(cid1) == 5); REQUIRE(timeline->getClipPosition(cid3) == 10 + length); REQUIRE(undoStack->index() == init_index + 3); }; state3(); REQUIRE(timeline->requestItemResize(cid3, 1, true) == 1); auto state4 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 2); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipTrackId(cid3) == tid1); REQUIRE(timeline->getClipPosition(cid1) == 5); REQUIRE(timeline->getClipPlaytime(cid3) == 1); REQUIRE(timeline->getClipPosition(cid3) == 10 + length); REQUIRE(undoStack->index() == init_index + 4); }; state4(); undoStack->undo(); state3(); undoStack->undo(); state2(); undoStack->undo(); state1(); undoStack->redo(); state2(); undoStack->redo(); state3(); undoStack->redo(); state4(); undoStack->undo(); state3(); undoStack->undo(); state2(); undoStack->undo(); state1(); } SECTION("Clip Deletion undo") { REQUIRE(timeline->requestClipMove(cid1, tid1, 5)); auto state1 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 1); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipPosition(cid1) == 5); REQUIRE(undoStack->index() == init_index + 1); }; state1(); int nbClips = timeline->getClipsCount(); REQUIRE(timeline->requestItemDeletion(cid1)); auto state2 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 0); REQUIRE(timeline->getClipsCount() == nbClips - 1); REQUIRE(undoStack->index() == init_index + 2); }; state2(); undoStack->undo(); state1(); undoStack->redo(); state2(); undoStack->undo(); state1(); } SECTION("Track insertion undo") { std::map orig_trackPositions, final_trackPositions; for (const auto &it : timeline->m_iteratorTable) { int track = it.first; int pos = timeline->getTrackPosition(track); orig_trackPositions[track] = pos; if (pos >= 1) pos++; final_trackPositions[track] = pos; } auto checkPositions = [&](const std::map &pos) { for (const auto &p : pos) { REQUIRE(timeline->getTrackPosition(p.first) == p.second); } }; checkPositions(orig_trackPositions); int new_tid; REQUIRE(timeline->requestTrackInsertion(1, new_tid)); checkPositions(final_trackPositions); undoStack->undo(); checkPositions(orig_trackPositions); undoStack->redo(); checkPositions(final_trackPositions); undoStack->undo(); checkPositions(orig_trackPositions); } SECTION("Track deletion undo") { int nb_clips = timeline->getClipsCount(); int nb_tracks = timeline->getTracksCount(); REQUIRE(timeline->requestClipMove(cid1, tid1, 5)); auto state1 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid1) == 1); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipPosition(cid1) == 5); REQUIRE(undoStack->index() == init_index + 1); REQUIRE(timeline->getClipsCount() == nb_clips); REQUIRE(timeline->getTracksCount() == nb_tracks); }; state1(); REQUIRE(timeline->requestTrackDeletion(tid1)); REQUIRE(timeline->getClipsCount() == nb_clips - 1); REQUIRE(timeline->getTracksCount() == nb_tracks - 1); undoStack->undo(); state1(); undoStack->redo(); REQUIRE(timeline->getClipsCount() == nb_clips - 1); REQUIRE(timeline->getTracksCount() == nb_tracks - 1); undoStack->undo(); state1(); } int clipCount = timeline->m_allClips.size(); SECTION("Clip creation and resize") { int cid6; auto state0 = [&]() { REQUIRE(timeline->m_allClips.size() == clipCount); REQUIRE(timeline->checkConsistency()); }; state0(); { std::function undo = []() { return true; }; std::function redo = []() { return true; }; REQUIRE(timeline->requestClipCreation(binId, cid6, PlaylistState::VideoOnly, 1., undo, redo)); pCore->pushUndo(undo, redo, QString()); } int l = timeline->getClipPlaytime(cid6); auto state1 = [&]() { REQUIRE(timeline->m_allClips.size() == clipCount + 1); REQUIRE(timeline->isClip(cid6)); REQUIRE(timeline->getClipTrackId(cid6) == -1); REQUIRE(timeline->getClipPlaytime(cid6) == l); }; state1(); { std::function undo = []() { return true; }; std::function redo = []() { return true; }; REQUIRE(timeline->requestItemResize(cid6, l - 5, true, true, undo, redo, false)); pCore->pushUndo(undo, redo, QString()); } auto state2 = [&]() { REQUIRE(timeline->m_allClips.size() == clipCount + 1); REQUIRE(timeline->isClip(cid6)); REQUIRE(timeline->getClipTrackId(cid6) == -1); REQUIRE(timeline->getClipPlaytime(cid6) == l - 5); }; state2(); { std::function undo = []() { return true; }; std::function redo = []() { return true; }; REQUIRE(timeline->requestClipMove(cid6, tid1, 7, true, true, undo, redo)); pCore->pushUndo(undo, redo, QString()); } auto state3 = [&]() { REQUIRE(timeline->m_allClips.size() == clipCount + 1); REQUIRE(timeline->isClip(cid6)); REQUIRE(timeline->getClipTrackId(cid6) == tid1); REQUIRE(timeline->getClipPosition(cid6) == 7); REQUIRE(timeline->getClipPlaytime(cid6) == l - 5); }; state3(); { std::function undo = []() { return true; }; std::function redo = []() { return true; }; REQUIRE(timeline->requestItemResize(cid6, l - 6, false, true, undo, redo, false)); pCore->pushUndo(undo, redo, QString()); } auto state4 = [&]() { REQUIRE(timeline->m_allClips.size() == clipCount + 1); REQUIRE(timeline->isClip(cid6)); REQUIRE(timeline->getClipTrackId(cid6) == tid1); REQUIRE(timeline->getClipPosition(cid6) == 8); REQUIRE(timeline->getClipPlaytime(cid6) == l - 6); }; state4(); undoStack->undo(); state3(); undoStack->undo(); state2(); undoStack->undo(); state1(); undoStack->undo(); state0(); undoStack->redo(); state1(); undoStack->redo(); state2(); undoStack->redo(); state3(); undoStack->redo(); state4(); } binModel->clean(); pCore->m_projectManager = nullptr; Logger::print_trace(); } TEST_CASE("Snapping", "[Snapping]") { Logger::clear(); auto binModel = pCore->projectItemModel(); binModel->clean(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); // Here we do some trickery to enable testing. // We mock the project class so that the undoStack function returns our undoStack Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; // We also mock timeline object to spy few functions and mock others TimelineItemModel tim(&profile_model, undoStack); Mock timMock(tim); auto timeline = std::shared_ptr(&timMock.get(), [](...) {}); TimelineItemModel::finishConstruct(timeline, guideModel); RESET(timMock); QString binId = createProducer(profile_model, "red", binModel, 50); QString binId2 = createProducer(profile_model, "blue", binModel); int tid1 = TrackModel::construct(timeline); int cid1 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly); int tid2 = TrackModel::construct(timeline); int cid2 = ClipModel::construct(timeline, binId2, -1, PlaylistState::VideoOnly); int cid3 = ClipModel::construct(timeline, binId2, -1, PlaylistState::VideoOnly); timeline->m_allClips[cid1]->m_endlessResize = false; timeline->m_allClips[cid2]->m_endlessResize = false; timeline->m_allClips[cid3]->m_endlessResize = false; int length = timeline->getClipPlaytime(cid1); int length2 = timeline->getClipPlaytime(cid2); SECTION("getBlankSizeNearClip") { REQUIRE(timeline->requestClipMove(cid1, tid1, 0)); // before REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid1, false) == 0); // after REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid1, true) == INT_MAX); REQUIRE(timeline->requestClipMove(cid1, tid1, 10)); // before REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid1, false) == 10); // after REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid1, true) == INT_MAX); REQUIRE(timeline->requestClipMove(cid2, tid1, 25 + length)); // before REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid1, false) == 10); REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid2, false) == 15); // after REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid1, true) == 15); REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid2, true) == INT_MAX); REQUIRE(timeline->requestClipMove(cid2, tid1, 10 + length)); // before REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid1, false) == 10); REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid2, false) == 0); // after REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid1, true) == 0); REQUIRE(timeline->getTrackById(tid1)->getBlankSizeNearClip(cid2, true) == INT_MAX); } SECTION("Snap move to a single clip") { int beg = 30; // in the absence of other clips, a valid move shouldn't be modified for (int snap = -1; snap <= 5; ++snap) { REQUIRE(timeline->suggestClipMove(cid2, tid2, beg, -1, snap) == beg); REQUIRE(timeline->suggestClipMove(cid2, tid2, beg + length, -1, snap) == beg + length); REQUIRE(timeline->checkConsistency()); } // We add a clip in first track to create snap points REQUIRE(timeline->requestClipMove(cid1, tid1, beg)); // Now a clip in second track should snap to beginning auto check_snap = [&](int pos, int perturb, int snap) { if (snap >= perturb) { REQUIRE(timeline->suggestClipMove(cid2, tid2, pos + perturb, -1, snap) == pos); REQUIRE(timeline->suggestClipMove(cid2, tid2, pos - perturb, -1, snap) == pos); } else { REQUIRE(timeline->suggestClipMove(cid2, tid2, pos + perturb, -1, snap) == pos + perturb); REQUIRE(timeline->suggestClipMove(cid2, tid2, pos - perturb, -1, snap) == pos - perturb); } }; for (int snap = -1; snap <= 5; ++snap) { for (int perturb = 0; perturb <= 6; ++perturb) { // snap to beginning check_snap(beg, perturb, snap); check_snap(beg + length, perturb, snap); // snap to end check_snap(beg - length2, perturb, snap); check_snap(beg + length - length2, perturb, snap); REQUIRE(timeline->checkConsistency()); } } // Same test, but now clip is moved in position 0 first REQUIRE(timeline->requestClipMove(cid2, tid2, 0)); for (int snap = -1; snap <= 5; ++snap) { for (int perturb = 0; perturb <= 6; ++perturb) { // snap to beginning check_snap(beg, perturb, snap); check_snap(beg + length, perturb, snap); // snap to end check_snap(beg - length2, perturb, snap); check_snap(beg + length - length2, perturb, snap); REQUIRE(timeline->checkConsistency()); } } } binModel->clean(); pCore->m_projectManager = nullptr; Logger::print_trace(); } TEST_CASE("Advanced trimming operations", "[Trimming]") { Logger::clear(); auto binModel = pCore->projectItemModel(); binModel->clean(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); // Here we do some trickery to enable testing. // We mock the project class so that the undoStack function returns our undoStack Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; // We also mock timeline object to spy few functions and mock others TimelineItemModel tim(&profile_model, undoStack); Mock timMock(tim); auto timeline = std::shared_ptr(&timMock.get(), [](...) {}); TimelineItemModel::finishConstruct(timeline, guideModel); RESET(timMock); QString binId = createProducer(profile_model, "red", binModel); QString binId2 = createProducer(profile_model, "blue", binModel); QString binId3 = createProducerWithSound(profile_model, binModel); int cid1 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly); int tid1 = TrackModel::construct(timeline); int tid2 = TrackModel::construct(timeline); int tid3 = TrackModel::construct(timeline); // Add an audio track int tid4 = TrackModel::construct(timeline, -1, -1, QString(), true); int cid2 = ClipModel::construct(timeline, binId2, -1, PlaylistState::VideoOnly); int cid3 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly); int cid4 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly); int cid5 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly); int cid6 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly); int cid7 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly); int audio1 = ClipModel::construct(timeline, binId3, -1, PlaylistState::VideoOnly); int audio2 = ClipModel::construct(timeline, binId3, -1, PlaylistState::VideoOnly); int audio3 = ClipModel::construct(timeline, binId3, -1, PlaylistState::VideoOnly); timeline->m_allClips[cid1]->m_endlessResize = false; timeline->m_allClips[cid2]->m_endlessResize = false; timeline->m_allClips[cid3]->m_endlessResize = false; timeline->m_allClips[cid4]->m_endlessResize = false; timeline->m_allClips[cid5]->m_endlessResize = false; timeline->m_allClips[cid6]->m_endlessResize = false; timeline->m_allClips[cid7]->m_endlessResize = false; SECTION("Clip cutting") { // Trivial split REQUIRE(timeline->requestClipMove(cid1, tid1, 0)); int l = timeline->getClipPlaytime(cid2); REQUIRE(timeline->requestItemResize(cid2, l - 3, true) == l - 3); REQUIRE(timeline->requestItemResize(cid2, l - 5, false) == l - 5); REQUIRE(timeline->requestClipMove(cid2, tid1, l)); REQUIRE(timeline->requestClipMove(cid3, tid1, l + l - 5)); auto state = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipPlaytime(cid1) == l); REQUIRE(timeline->getClipPlaytime(cid2) == l - 5); REQUIRE(timeline->getClipPlaytime(cid3) == l); REQUIRE(timeline->getClipPosition(cid1) == 0); REQUIRE(timeline->getClipPosition(cid2) == l); REQUIRE(timeline->getClipPosition(cid3) == l + l - 5); REQUIRE(timeline->getClipPtr(cid2)->getIn() == 2); REQUIRE(timeline->getClipPtr(cid2)->getOut() == l - 4); }; state(); // require cut position outside the clip REQUIRE_FALSE(TimelineFunctions::requestClipCut(timeline, cid2, 0)); REQUIRE_FALSE(TimelineFunctions::requestClipCut(timeline, cid2, 5 * l)); // can't cut on edges either REQUIRE_FALSE(TimelineFunctions::requestClipCut(timeline, cid2, l)); REQUIRE_FALSE(TimelineFunctions::requestClipCut(timeline, cid2, l + l - 5)); state(); REQUIRE(TimelineFunctions::requestClipCut(timeline, cid2, l + 4)); int splitted = timeline->getClipByPosition(tid1, l + 5); auto state2 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipPlaytime(cid1) == l); REQUIRE(timeline->getClipPlaytime(cid2) == 4); REQUIRE(timeline->getClipPlaytime(splitted) == l - 9); REQUIRE(timeline->getClipPlaytime(cid3) == l); REQUIRE(timeline->getClipPosition(cid1) == 0); REQUIRE(timeline->getClipPosition(cid2) == l); REQUIRE(timeline->getClipPosition(splitted) == l + 4); REQUIRE(timeline->getClipPosition(cid3) == l + l - 5); REQUIRE(timeline->getClipPtr(cid2)->getIn() == 2); REQUIRE(timeline->getClipPtr(cid2)->getOut() == 5); REQUIRE(timeline->getClipPtr(splitted)->getIn() == 6); REQUIRE(timeline->getClipPtr(splitted)->getOut() == l - 4); }; state2(); undoStack->undo(); state(); undoStack->redo(); state2(); } SECTION("Cut and resize") { REQUIRE(timeline->requestClipMove(cid1, tid1, 5)); int l = timeline->getClipPlaytime(cid1); timeline->m_allClips[cid1]->m_endlessResize = false; auto state = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipPlaytime(cid1) == l); REQUIRE(timeline->getClipPosition(cid1) == 5); }; state(); REQUIRE(TimelineFunctions::requestClipCut(timeline, cid1, 9)); int splitted = timeline->getClipByPosition(tid1, 10); timeline->m_allClips[splitted]->m_endlessResize = false; auto state2 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipTrackId(splitted) == tid1); REQUIRE(timeline->getClipPlaytime(cid1) == 4); REQUIRE(timeline->getClipPlaytime(splitted) == l - 4); REQUIRE(timeline->getClipPosition(cid1) == 5); REQUIRE(timeline->getClipPosition(splitted) == 9); }; state2(); REQUIRE(timeline->requestClipMove(splitted, tid2, 9, true, true)); REQUIRE(timeline->requestItemResize(splitted, l - 3, true, true) == -1); REQUIRE(timeline->requestItemResize(splitted, l, false, true) == l); REQUIRE(timeline->requestItemResize(cid1, 5, false, true) == -1); REQUIRE(timeline->requestItemResize(cid1, l, true, true) == l); auto state3 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipTrackId(cid1) == tid1); REQUIRE(timeline->getClipTrackId(splitted) == tid2); REQUIRE(timeline->getClipPlaytime(cid1) == l); REQUIRE(timeline->getClipPlaytime(splitted) == l); REQUIRE(timeline->getClipPosition(cid1) == 5); REQUIRE(timeline->getClipPosition(splitted) == 5); }; state3(); undoStack->undo(); undoStack->undo(); undoStack->undo(); state2(); undoStack->undo(); state(); undoStack->redo(); state2(); undoStack->redo(); undoStack->redo(); undoStack->redo(); state3(); } SECTION("Clip cutting 2") { // More complex group structure split split int l = timeline->getClipPlaytime(cid2); REQUIRE(timeline->requestClipMove(cid1, tid1, 0)); REQUIRE(timeline->requestClipMove(cid2, tid1, l)); REQUIRE(timeline->requestClipMove(cid3, tid1, 2 * l)); REQUIRE(timeline->requestClipMove(cid4, tid2, 0)); REQUIRE(timeline->requestClipMove(cid5, tid2, l)); REQUIRE(timeline->requestClipMove(cid6, tid2, 2 * l)); REQUIRE(timeline->requestClipMove(cid7, tid1, 200)); int gid1 = timeline->requestClipsGroup(std::unordered_set({cid1, cid4}), true, GroupType::Normal); int gid2 = timeline->requestClipsGroup(std::unordered_set({cid2, cid5}), true, GroupType::Normal); int gid3 = timeline->requestClipsGroup(std::unordered_set({cid3, cid6}), true, GroupType::Normal); int gid4 = timeline->requestClipsGroup(std::unordered_set({cid1, cid2, cid3, cid4, cid5, cid6, cid7}), true, GroupType::Normal); auto state = [&]() { REQUIRE(timeline->checkConsistency()); int p = 0; for (int c : std::vector({cid1, cid2, cid3})) { REQUIRE(timeline->getClipPlaytime(c) == l); REQUIRE(timeline->getClipTrackId(c) == tid1); REQUIRE(timeline->getClipPosition(c) == p); p += l; } p = 0; for (int c : std::vector({cid4, cid5, cid6})) { REQUIRE(timeline->getClipPlaytime(c) == l); REQUIRE(timeline->getClipTrackId(c) == tid2); REQUIRE(timeline->getClipPosition(c) == p); p += l; } REQUIRE(timeline->getClipPosition(cid7) == 200); REQUIRE(timeline->getClipTrackId(cid7) == tid1); REQUIRE(timeline->m_groups->getDirectChildren(gid1) == std::unordered_set({cid1, cid4})); REQUIRE(timeline->m_groups->getDirectChildren(gid2) == std::unordered_set({cid2, cid5})); REQUIRE(timeline->m_groups->getDirectChildren(gid3) == std::unordered_set({cid3, cid6})); REQUIRE(timeline->m_groups->getDirectChildren(gid4) == std::unordered_set({gid1, gid2, gid3, cid7})); REQUIRE(timeline->getGroupElements(cid1) == std::unordered_set({cid1, cid2, cid3, cid4, cid5, cid6, cid7})); }; state(); REQUIRE_FALSE(TimelineFunctions::requestClipCut(timeline, cid2, 0)); REQUIRE_FALSE(TimelineFunctions::requestClipCut(timeline, cid2, 5 * l)); REQUIRE_FALSE(TimelineFunctions::requestClipCut(timeline, cid2, l)); REQUIRE_FALSE(TimelineFunctions::requestClipCut(timeline, cid2, 2 * l)); state(); REQUIRE(TimelineFunctions::requestClipCut(timeline, cid2, l + 4)); int splitted = timeline->getClipByPosition(tid1, l + 5); int splitted2 = timeline->getClipByPosition(tid2, l + 5); REQUIRE(splitted != splitted2); auto check_groups = [&]() { REQUIRE(timeline->m_groups->getDirectChildren(gid2) == std::unordered_set({splitted, splitted2})); REQUIRE(timeline->m_groups->getDirectChildren(gid3) == std::unordered_set({cid3, cid6})); REQUIRE(timeline->m_groups->getDirectChildren(gid4) == std::unordered_set({gid2, gid3, cid7})); REQUIRE(timeline->getGroupElements(cid3) == std::unordered_set({splitted, splitted2, cid3, cid6, cid7})); int g1b = timeline->m_groups->m_upLink[cid1]; int g2b = timeline->m_groups->m_upLink[cid2]; int g4b = timeline->m_groups->getRootId(cid1); REQUIRE(timeline->m_groups->getDirectChildren(g1b) == std::unordered_set({cid1, cid4})); REQUIRE(timeline->m_groups->getDirectChildren(g2b) == std::unordered_set({cid2, cid5})); REQUIRE(timeline->m_groups->getDirectChildren(g4b) == std::unordered_set({g1b, g2b})); REQUIRE(timeline->getGroupElements(cid1) == std::unordered_set({cid1, cid2, cid4, cid5})); }; auto state2 = [&]() { REQUIRE(timeline->checkConsistency()); int p = 0; for (int c : std::vector({cid1, cid2, cid3})) { REQUIRE(timeline->getClipPlaytime(c) == (c == cid2 ? 4 : l)); REQUIRE(timeline->getClipTrackId(c) == tid1); REQUIRE(timeline->getClipPosition(c) == p); p += l; } p = 0; for (int c : std::vector({cid4, cid5, cid6})) { REQUIRE(timeline->getClipPlaytime(c) == (c == cid5 ? 4 : l)); REQUIRE(timeline->getClipTrackId(c) == tid2); REQUIRE(timeline->getClipPosition(c) == p); p += l; } REQUIRE(timeline->getClipPosition(cid7) == 200); REQUIRE(timeline->getClipTrackId(cid7) == tid1); REQUIRE(timeline->getClipPosition(splitted) == l + 4); REQUIRE(timeline->getClipPlaytime(splitted) == l - 4); REQUIRE(timeline->getClipTrackId(splitted) == tid1); REQUIRE(timeline->getClipPosition(splitted2) == l + 4); REQUIRE(timeline->getClipPlaytime(splitted2) == l - 4); REQUIRE(timeline->getClipTrackId(splitted2) == tid2); check_groups(); }; state2(); REQUIRE(timeline->requestClipMove(splitted, tid1, l + 4 + 10, true, true)); REQUIRE(timeline->requestClipMove(cid1, tid2, 10, true, true)); auto state3 = [&]() { REQUIRE(timeline->checkConsistency()); int p = 0; for (int c : std::vector({cid1, cid2, cid3})) { REQUIRE(timeline->getClipPlaytime(c) == (c == cid2 ? 4 : l)); REQUIRE(timeline->getClipTrackId(c) == (c == cid3 ? tid1 : tid2)); REQUIRE(timeline->getClipPosition(c) == p + 10); p += l; } p = 0; for (int c : std::vector({cid4, cid5, cid6})) { REQUIRE(timeline->getClipPlaytime(c) == (c == cid5 ? 4 : l)); REQUIRE(timeline->getClipTrackId(c) == (c == cid6 ? tid2 : tid3)); REQUIRE(timeline->getClipPosition(c) == p + 10); p += l; } REQUIRE(timeline->getClipPosition(cid7) == 210); REQUIRE(timeline->getClipTrackId(cid7) == tid1); REQUIRE(timeline->getClipPosition(splitted) == l + 4 + 10); REQUIRE(timeline->getClipPlaytime(splitted) == l - 4); REQUIRE(timeline->getClipTrackId(splitted) == tid1); REQUIRE(timeline->getClipPosition(splitted2) == l + 4 + 10); REQUIRE(timeline->getClipPlaytime(splitted2) == l - 4); REQUIRE(timeline->getClipTrackId(splitted2) == tid2); check_groups(); }; state3(); undoStack->undo(); undoStack->undo(); state2(); undoStack->undo(); state(); undoStack->redo(); state2(); undoStack->redo(); undoStack->redo(); state3(); } SECTION("Simple audio split") { int l = timeline->getClipPlaytime(audio1); REQUIRE(timeline->requestClipMove(audio1, tid1, 3)); auto state = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipPlaytime(audio1) == l); REQUIRE(timeline->getClipPosition(audio1) == 3); REQUIRE(timeline->getClipTrackId(audio1) == tid1); REQUIRE(timeline->getTrackClipsCount(tid1) == 1); REQUIRE(timeline->getTrackClipsCount(tid2) == 0); REQUIRE(timeline->getGroupElements(audio1) == std::unordered_set({audio1})); }; state(); REQUIRE(TimelineFunctions::requestSplitAudio(timeline, audio1, tid4)); int splitted1 = timeline->getClipByPosition(tid4, 3); auto state2 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipPlaytime(audio1) == l); REQUIRE(timeline->getClipPosition(audio1) == 3); REQUIRE(timeline->getClipPlaytime(splitted1) == l); REQUIRE(timeline->getClipPosition(splitted1) == 3); REQUIRE(timeline->getClipTrackId(audio1) == tid1); REQUIRE(timeline->getClipTrackId(splitted1) == tid4); REQUIRE(timeline->getTrackClipsCount(tid1) == 1); REQUIRE(timeline->getTrackClipsCount(tid4) == 1); REQUIRE(timeline->getGroupElements(audio1) == std::unordered_set({audio1, splitted1})); int g1 = timeline->m_groups->getDirectAncestor(audio1); REQUIRE(timeline->m_groups->getDirectChildren(g1) == std::unordered_set({audio1, splitted1})); REQUIRE(timeline->m_groups->getType(g1) == GroupType::AVSplit); }; state2(); undoStack->undo(); state(); undoStack->redo(); state2(); undoStack->undo(); state(); undoStack->redo(); state2(); // We also make sure that clips that are audio only cannot be further splitted REQUIRE(timeline->requestClipMove(cid1, tid1, l + 30)); // This is a color clip, shouldn't be splittable REQUIRE_FALSE(TimelineFunctions::requestSplitAudio(timeline, cid1, tid2)); // Check we cannot split audio on a video track REQUIRE_FALSE(TimelineFunctions::requestSplitAudio(timeline, audio1, tid2)); } SECTION("Split audio on a selection") { int l = timeline->getClipPlaytime(audio2); REQUIRE(timeline->requestClipMove(audio1, tid1, 0)); REQUIRE(timeline->requestClipMove(audio2, tid1, l)); REQUIRE(timeline->requestClipMove(audio3, tid1, 2 * l)); std::unordered_set selection{audio1, audio3, audio2}; REQUIRE(timeline->requestClipsGroup(selection, false, GroupType::Selection)); auto state = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipPlaytime(audio1) == l); REQUIRE(timeline->getClipPlaytime(audio2) == l); REQUIRE(timeline->getClipPlaytime(audio3) == l); REQUIRE(timeline->getClipPosition(audio1) == 0); REQUIRE(timeline->getClipPosition(audio2) == l); REQUIRE(timeline->getClipPosition(audio3) == l + l); REQUIRE(timeline->getClipTrackId(audio1) == tid1); REQUIRE(timeline->getClipTrackId(audio2) == tid1); REQUIRE(timeline->getClipTrackId(audio3) == tid1); REQUIRE(timeline->getTrackClipsCount(tid1) == 3); REQUIRE(timeline->getTrackClipsCount(tid2) == 0); REQUIRE(timeline->getGroupElements(audio1) == std::unordered_set({audio1, audio2, audio3})); int sel = timeline->m_temporarySelectionGroup; // check that selection is preserved REQUIRE(sel != -1); REQUIRE(timeline->m_groups->getType(sel) == GroupType::Selection); }; state(); REQUIRE(TimelineFunctions::requestSplitAudio(timeline, audio1, tid4)); int splitted1 = timeline->getClipByPosition(tid4, 0); int splitted2 = timeline->getClipByPosition(tid4, l); int splitted3 = timeline->getClipByPosition(tid4, 2 * l); auto state2 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipPlaytime(audio1) == l); REQUIRE(timeline->getClipPlaytime(audio2) == l); REQUIRE(timeline->getClipPlaytime(audio3) == l); REQUIRE(timeline->getClipPosition(audio1) == 0); REQUIRE(timeline->getClipPosition(audio2) == l); REQUIRE(timeline->getClipPosition(audio3) == l + l); REQUIRE(timeline->getClipPlaytime(splitted1) == l); REQUIRE(timeline->getClipPlaytime(splitted2) == l); REQUIRE(timeline->getClipPlaytime(splitted3) == l); REQUIRE(timeline->getClipPosition(splitted1) == 0); REQUIRE(timeline->getClipPosition(splitted2) == l); REQUIRE(timeline->getClipPosition(splitted3) == l + l); REQUIRE(timeline->getClipTrackId(audio1) == tid1); REQUIRE(timeline->getClipTrackId(audio2) == tid1); REQUIRE(timeline->getClipTrackId(audio3) == tid1); REQUIRE(timeline->getClipTrackId(splitted1) == tid4); REQUIRE(timeline->getClipTrackId(splitted2) == tid4); REQUIRE(timeline->getClipTrackId(splitted3) == tid4); REQUIRE(timeline->getTrackClipsCount(tid1) == 3); REQUIRE(timeline->getTrackClipsCount(tid4) == 3); REQUIRE(timeline->getGroupElements(audio1) == std::unordered_set({audio1, splitted1, audio2, audio3, splitted2, splitted3})); int sel = timeline->m_temporarySelectionGroup; // check that selection is preserved REQUIRE(sel != -1); REQUIRE(timeline->m_groups->getType(sel) == GroupType::Selection); REQUIRE(timeline->m_groups->getRootId(audio1) == sel); REQUIRE(timeline->m_groups->getDirectChildren(sel).size() == 3); REQUIRE(timeline->m_groups->getLeaves(sel).size() == 6); int g1 = timeline->m_groups->getDirectAncestor(audio1); int g2 = timeline->m_groups->getDirectAncestor(audio2); int g3 = timeline->m_groups->getDirectAncestor(audio3); REQUIRE(timeline->m_groups->getDirectChildren(sel) == std::unordered_set({g1, g2, g3})); REQUIRE(timeline->m_groups->getDirectChildren(g1) == std::unordered_set({audio1, splitted1})); REQUIRE(timeline->m_groups->getDirectChildren(g2) == std::unordered_set({audio2, splitted2})); REQUIRE(timeline->m_groups->getDirectChildren(g3) == std::unordered_set({audio3, splitted3})); REQUIRE(timeline->m_groups->getType(g1) == GroupType::AVSplit); REQUIRE(timeline->m_groups->getType(g2) == GroupType::AVSplit); REQUIRE(timeline->m_groups->getType(g3) == GroupType::AVSplit); }; state2(); undoStack->undo(); state(); undoStack->redo(); state2(); } SECTION("Cut should preserve AV groups") { QString binId3 = createProducerWithSound(profile_model, binModel); int tid6 = TrackModel::construct(timeline, -1, -1, QString(), true); int tid5 = TrackModel::construct(timeline); int cid6 = -1; int pos = 3; REQUIRE(timeline->requestClipInsertion(binId3, tid5, pos, cid6, true, true, false)); int cid7 = timeline->m_groups->getSplitPartner(cid6); int l = timeline->getClipPlaytime(cid6); REQUIRE(l >= 10); auto state = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid5) == 1); REQUIRE(timeline->getTrackClipsCount(tid6) == 1); REQUIRE(timeline->getClipTrackId(cid6) == tid5); REQUIRE(timeline->getClipTrackId(cid7) == tid6); REQUIRE(timeline->getClipPosition(cid6) == pos); REQUIRE(timeline->getClipPosition(cid7) == pos); REQUIRE(timeline->getClipPtr(cid6)->clipState() == PlaylistState::VideoOnly); REQUIRE(timeline->getClipPtr(cid7)->clipState() == PlaylistState::AudioOnly); // we check that the av group was correctly created REQUIRE(timeline->getGroupElements(cid6) == std::unordered_set({cid6, cid7})); int g1 = timeline->m_groups->getDirectAncestor(cid6); REQUIRE(timeline->m_groups->getDirectChildren(g1) == std::unordered_set({cid6, cid7})); REQUIRE(timeline->m_groups->getType(g1) == GroupType::AVSplit); }; state(); REQUIRE(TimelineFunctions::requestClipCut(timeline, cid6, pos + 4)); int cid8 = timeline->getClipByPosition(tid5, pos + 5); int cid9 = timeline->getClipByPosition(tid6, pos + 5); REQUIRE(cid8 >= 0); REQUIRE(cid9 >= 0); auto state2 = [&]() { REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getTrackClipsCount(tid5) == 2); REQUIRE(timeline->getTrackClipsCount(tid6) == 2); REQUIRE(timeline->getClipTrackId(cid6) == tid5); REQUIRE(timeline->getClipTrackId(cid7) == tid6); REQUIRE(timeline->getClipTrackId(cid8) == tid5); REQUIRE(timeline->getClipTrackId(cid9) == tid6); REQUIRE(timeline->getClipPosition(cid6) == pos); REQUIRE(timeline->getClipPosition(cid7) == pos); REQUIRE(timeline->getClipPosition(cid8) == pos + 4); REQUIRE(timeline->getClipPosition(cid9) == pos + 4); REQUIRE(timeline->getClipPtr(cid6)->clipState() == PlaylistState::VideoOnly); REQUIRE(timeline->getClipPtr(cid7)->clipState() == PlaylistState::AudioOnly); REQUIRE(timeline->getClipPtr(cid8)->clipState() == PlaylistState::VideoOnly); REQUIRE(timeline->getClipPtr(cid9)->clipState() == PlaylistState::AudioOnly); // original AV group REQUIRE(timeline->getGroupElements(cid6) == std::unordered_set({cid6, cid7})); int g1 = timeline->m_groups->getDirectAncestor(cid6); REQUIRE(timeline->m_groups->getDirectChildren(g1) == std::unordered_set({cid6, cid7})); REQUIRE(timeline->m_groups->getType(g1) == GroupType::AVSplit); // new AV group REQUIRE(timeline->getGroupElements(cid8) == std::unordered_set({cid8, cid9})); int g2 = timeline->m_groups->getDirectAncestor(cid8); REQUIRE(timeline->m_groups->getDirectChildren(g2) == std::unordered_set({cid8, cid9})); REQUIRE(timeline->m_groups->getType(g2) == GroupType::AVSplit); }; state2(); undoStack->undo(); state(); undoStack->redo(); state2(); } binModel->clean(); pCore->m_projectManager = nullptr; Logger::print_trace(); } diff --git a/tests/regressions.cpp b/tests/regressions.cpp index 12d698b98..3887f89dd 100644 --- a/tests/regressions.cpp +++ b/tests/regressions.cpp @@ -1,633 +1,641 @@ #include "test_utils.hpp" Mlt::Profile reg_profile; TEST_CASE("Regression") { + Logger::clear(); auto binModel = pCore->projectItemModel(); binModel->clean(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); // Here we do some trickery to enable testing. // We mock the project class so that the undoStack function returns our undoStack Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; // We also mock timeline object to spy few functions and mock others TimelineItemModel tim(®_profile, undoStack); Mock timMock(tim); auto timeline = std::shared_ptr(&timMock.get(), [](...) {}); TimelineItemModel::finishConstruct(timeline, guideModel); RESET(timMock); TimelineModel::next_id = 0; undoStack->undo(); undoStack->redo(); undoStack->redo(); undoStack->undo(); QString binId0 = createProducer(reg_profile, "red", binModel); int c = ClipModel::construct(timeline, binId0, -1, PlaylistState::VideoOnly); timeline->m_allClips[c]->m_endlessResize = false; TrackModel::construct(timeline); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->requestClipMove(0, 1, 0)); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->requestItemResize(0, 16, false)); REQUIRE(timeline->getTrackById(1)->checkConsistency()); undoStack->undo(); REQUIRE(timeline->getTrackById(1)->checkConsistency()); undoStack->redo(); REQUIRE(timeline->getTrackById(1)->checkConsistency()); undoStack->undo(); REQUIRE(timeline->getTrackById(1)->checkConsistency()); undoStack->redo(); REQUIRE(timeline->getTrackById(1)->checkConsistency()); undoStack->undo(); REQUIRE(timeline->getTrackById(1)->checkConsistency()); undoStack->redo(); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->requestItemResize(0, 0, false) == -1); REQUIRE(timeline->getTrackById(1)->checkConsistency()); TrackModel::construct(timeline); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); undoStack->undo(); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); undoStack->redo(); REQUIRE(timeline->getTrackById(1)->checkConsistency()); binModel->clean(); pCore->m_projectManager = nullptr; + Logger::print_trace(); } TEST_CASE("Regression2") { + Logger::clear(); auto binModel = pCore->projectItemModel(); binModel->clean(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); // Here we do some trickery to enable testing. // We mock the project class so that the undoStack function returns our undoStack Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; // We also mock timeline object to spy few functions and mock others TimelineItemModel tim(®_profile, undoStack); Mock timMock(tim); auto timeline = std::shared_ptr(&timMock.get(), [](...) {}); TimelineItemModel::finishConstruct(timeline, guideModel); RESET(timMock); TimelineModel::next_id = 0; int dummy_id; undoStack->undo(); undoStack->undo(); undoStack->redo(); TrackModel::construct(timeline); REQUIRE(timeline->getTrackById(0)->checkConsistency()); undoStack->undo(); REQUIRE(timeline->getTrackById(0)->checkConsistency()); { QString binId0 = createProducer(reg_profile, "red", binModel); bool ok = timeline->requestClipInsertion(binId0, 0, 10, dummy_id); timeline->m_allClips[dummy_id]->m_endlessResize = false; REQUIRE(ok); } REQUIRE(timeline->getTrackById(0)->checkConsistency()); undoStack->undo(); REQUIRE(timeline->getTrackById(0)->checkConsistency()); undoStack->redo(); REQUIRE(timeline->getTrackById(0)->checkConsistency()); TrackModel::construct(timeline); REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); { QString binId0 = createProducer(reg_profile, "red", binModel); bool ok = timeline->requestClipInsertion(binId0, 2, 10, dummy_id); timeline->m_allClips[3]->m_endlessResize = false; REQUIRE(ok); } REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); { bool ok = timeline->requestClipMove(1, 0, 10); REQUIRE(ok); } REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); undoStack->undo(); REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); undoStack->redo(); timeline->m_allClips[3]->m_endlessResize = false; REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); { REQUIRE(timeline->requestItemResize(3, 0, false) == -1); } REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); TrackModel::construct(timeline); REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(4)->checkConsistency()); { QString binId0 = createProducer(reg_profile, "red", binModel); int c = ClipModel::construct(timeline, binId0, -1, PlaylistState::VideoOnly); timeline->m_allClips[c]->m_endlessResize = false; } REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(4)->checkConsistency()); TrackModel::construct(timeline); REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(4)->checkConsistency()); REQUIRE(timeline->getTrackById(6)->checkConsistency()); { REQUIRE(timeline->requestItemResize(3, 15, true) > -1); } REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(4)->checkConsistency()); REQUIRE(timeline->getTrackById(6)->checkConsistency()); { bool ok = timeline->requestClipMove(3, 0, 0); REQUIRE_FALSE(ok); } REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(4)->checkConsistency()); REQUIRE(timeline->getTrackById(6)->checkConsistency()); { REQUIRE(timeline->requestItemResize(3, 16, false) == -1); } REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(4)->checkConsistency()); REQUIRE(timeline->getTrackById(6)->checkConsistency()); { REQUIRE(timeline->requestItemResize(3, 16, true) > -1); } REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(4)->checkConsistency()); REQUIRE(timeline->getTrackById(6)->checkConsistency()); undoStack->undo(); REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(4)->checkConsistency()); REQUIRE(timeline->getTrackById(6)->checkConsistency()); undoStack->undo(); REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(4)->checkConsistency()); REQUIRE(timeline->getTrackById(6)->checkConsistency()); { QString binId0 = createProducer(reg_profile, "red", binModel); bool ok = timeline->requestClipInsertion(binId0, 0, 1, dummy_id); REQUIRE_FALSE(ok); } REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(4)->checkConsistency()); REQUIRE(timeline->getTrackById(6)->checkConsistency()); undoStack->undo(); REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(4)->checkConsistency()); REQUIRE(timeline->getTrackById(6)->checkConsistency()); undoStack->redo(); REQUIRE(timeline->getTrackById(0)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(4)->checkConsistency()); REQUIRE(timeline->getTrackById(6)->checkConsistency()); undoStack->redo(); binModel->clean(); pCore->m_projectManager = nullptr; + Logger::print_trace(); } /* TEST_CASE("Regression 3") { Mlt::Profile profile; std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr timeline = TimelineItemModel::construct(new Mlt::Profile(), undoStack); TimelineModel::next_id = 0; int dummy_id; std::shared_ptr producer0 = std::make_shared(profile, "color", "red"); producer0->set("length", 20); producer0->set("out", 19); ClipModel::construct(timeline, producer0 ); { bool ok = timeline->requestTrackInsertion(-1, dummy_id); REQUIRE(ok); } TrackModel::construct(timeline); TrackModel::construct(timeline); std::shared_ptr producer1 = std::make_shared(profile, "color", "red"); producer1->set("length", 20); producer1->set("out", 19); ClipModel::construct(timeline, producer1 ); std::shared_ptr producer2 = std::make_shared(profile, "color", "red"); producer2->set("length", 20); producer2->set("out", 19); ClipModel::construct(timeline, producer2 ); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); std::shared_ptr producer3 = std::make_shared(profile, "color", "red"); producer3->set("length", 20); producer3->set("out", 19); ClipModel::construct(timeline, producer3 ); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); TrackModel::construct(timeline); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); REQUIRE(timeline->getTrackById(7)->checkConsistency()); TrackModel::construct(timeline); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); REQUIRE(timeline->getTrackById(7)->checkConsistency()); REQUIRE(timeline->getTrackById(8)->checkConsistency()); TrackModel::construct(timeline); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); REQUIRE(timeline->getTrackById(7)->checkConsistency()); REQUIRE(timeline->getTrackById(8)->checkConsistency()); REQUIRE(timeline->getTrackById(9)->checkConsistency()); std::shared_ptr producer4 = std::make_shared(profile, "color", "red"); producer4->set("length", 20); producer4->set("out", 19); ClipModel::construct(timeline, producer4 ); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); REQUIRE(timeline->getTrackById(7)->checkConsistency()); REQUIRE(timeline->getTrackById(8)->checkConsistency()); REQUIRE(timeline->getTrackById(9)->checkConsistency()); std::shared_ptr producer5 = std::make_shared(profile, "color", "red"); producer5->set("length", 20); producer5->set("out", 19); ClipModel::construct(timeline, producer5 ); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); REQUIRE(timeline->getTrackById(7)->checkConsistency()); REQUIRE(timeline->getTrackById(8)->checkConsistency()); REQUIRE(timeline->getTrackById(9)->checkConsistency()); std::shared_ptr producer6 = std::make_shared(profile, "color", "red"); producer6->set("length", 20); producer6->set("out", 19); ClipModel::construct(timeline, producer6 ); REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); REQUIRE(timeline->getTrackById(7)->checkConsistency()); REQUIRE(timeline->getTrackById(8)->checkConsistency()); REQUIRE(timeline->getTrackById(9)->checkConsistency()); { bool ok = timeline->requestClipMove(0,1 ,10 ); REQUIRE(ok); } REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); REQUIRE(timeline->getTrackById(7)->checkConsistency()); REQUIRE(timeline->getTrackById(8)->checkConsistency()); REQUIRE(timeline->getTrackById(9)->checkConsistency()); { bool ok = timeline->requestClipMove(4,2 ,12 ); REQUIRE(ok); } REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); REQUIRE(timeline->getTrackById(7)->checkConsistency()); REQUIRE(timeline->getTrackById(8)->checkConsistency()); REQUIRE(timeline->getTrackById(9)->checkConsistency()); { auto group = {4, 0}; bool ok = timeline->requestClipsGroup(group); REQUIRE(ok); } REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); REQUIRE(timeline->getTrackById(7)->checkConsistency()); REQUIRE(timeline->getTrackById(8)->checkConsistency()); REQUIRE(timeline->getTrackById(9)->checkConsistency()); { bool ok = timeline->requestClipMove(4,1 ,10 ); REQUIRE_FALSE(ok); } REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); REQUIRE(timeline->getTrackById(7)->checkConsistency()); REQUIRE(timeline->getTrackById(8)->checkConsistency()); REQUIRE(timeline->getTrackById(9)->checkConsistency()); { bool ok = timeline->requestClipMove(4,1 ,100 ); REQUIRE_FALSE(ok); } REQUIRE(timeline->getTrackById(1)->checkConsistency()); REQUIRE(timeline->getTrackById(2)->checkConsistency()); REQUIRE(timeline->getTrackById(3)->checkConsistency()); REQUIRE(timeline->getTrackById(7)->checkConsistency()); REQUIRE(timeline->getTrackById(8)->checkConsistency()); REQUIRE(timeline->getTrackById(9)->checkConsistency()); { bool ok = timeline->requestClipMove(0,3 ,100 ); REQUIRE(ok); } std::shared_ptr producer7 = std::make_shared(profile, "color", "red"); producer7->set("length", 20); producer7->set("out", 19); ClipModel::construct(timeline, producer7 ); { bool ok = timeline->requestTrackInsertion(-1, dummy_id); REQUIRE(ok); } undoStack->undo(); { bool ok = timeline->requestClipMove(0,1 ,5 ); REQUIRE(ok); } { bool ok = timeline->requestTrackDeletion(1); REQUIRE(ok); } } TEST_CASE("Regression 4") { Mlt::Profile profile; std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr timeline = TimelineItemModel::construct(new Mlt::Profile(), undoStack); TimelineModel::next_id = 0; int dummy_id; timeline->requestTrackInsertion(-1, dummy_id ); timeline->requestTrackInsertion(-1, dummy_id ); timeline->requestTrackInsertion(-1, dummy_id ); timeline->requestTrackInsertion(-1, dummy_id ); timeline->requestTrackInsertion(-1, dummy_id ); timeline->requestTrackInsertion(-1, dummy_id ); timeline->requestTrackInsertion(-1, dummy_id ); timeline->requestTrackInsertion(-1, dummy_id ); timeline->requestTrackInsertion(-1, dummy_id ); timeline->requestTrackInsertion(-1, dummy_id ); timeline->requestTrackInsertion(-1, dummy_id ); { std::shared_ptr producer = std::make_shared(profile, "color", "red"); producer->set("length", 62); producer->set("out", 61); timeline->requestClipInsertion(producer,10 ,453, dummy_id ); } timeline->requestClipMove(11,10 ,453, true, true ); { std::shared_ptr producer = std::make_shared(profile, "color", "red"); producer->set("length", 62); producer->set("out", 61); timeline->requestClipInsertion(producer,9 ,590, dummy_id ); } timeline->requestItemResize(11,62 ,true, false, true ); timeline->requestItemResize(11,62 ,true, true, true ); timeline->requestClipMove(11,10 ,507, true, true ); timeline->requestClipMove(12,10 ,583, false, false ); timeline->requestClipMove(12,9 ,521, true, true ); } */ TEST_CASE("FuzzBug1") { + Logger::clear(); auto binModel = pCore->projectItemModel(); 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; TimelineItemModel tim_0(®_profile, undoStack); Mock timMock_0(tim_0); auto timeline_0 = std::shared_ptr(&timMock_0.get(), [](...) {}); TimelineItemModel::finishConstruct(timeline_0, guideModel); Fake(Method(timMock_0, adjustAssetRange)); REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); TrackModel::construct(timeline_0, -1, -1, "", false); REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); TrackModel::construct(timeline_0, -1, -1, "$$$", false); REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { bool res = timeline_0->requestTrackDeletion(2); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { bool res = timeline_0->requestTrackDeletion(1); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int dummy_1; bool res = timeline_0->requestTrackInsertion(-1, dummy_1, "", false); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int dummy_1; bool res = timeline_0->requestTrackInsertion(-1, dummy_1, "", false); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); createProducer(reg_profile, "red", binModel, 20, true); REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int dummy_3; bool res = timeline_0->requestClipInsertion("2", 3, 0, dummy_3, true, false, true); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int dummy_3; bool res = timeline_0->requestClipInsertion("2", 3, 20, dummy_3, true, false, true); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int dummy_3; bool res = timeline_0->requestClipInsertion("2", 3, 40, dummy_3, true, false, true); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int res = timeline_0->requestClipsGroup({5, 7}, true, GroupType::Normal); REQUIRE(res == 8); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int res = timeline_0->requestClipsGroup({6, 7}, true, GroupType::Normal); REQUIRE(res == 9); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int res = timeline_0->requestClipsGroup({6, 7}, false, GroupType::Normal); REQUIRE(res == 9); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); } pCore->m_projectManager = nullptr; + Logger::print_trace(); } TEST_CASE("FuzzBug2") { + Logger::clear(); auto binModel = pCore->projectItemModel(); 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; TimelineItemModel tim_0(®_profile, undoStack); Mock timMock_0(tim_0); auto timeline_0 = std::shared_ptr(&timMock_0.get(), [](...) {}); TimelineItemModel::finishConstruct(timeline_0, guideModel); Fake(Method(timMock_0, adjustAssetRange)); REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int dummy_1; bool res = timeline_0->requestTrackInsertion(-1, dummy_1, "$", false); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); createProducer(reg_profile, "d", binModel, 0, true); REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int dummy_3; bool res = timeline_0->requestClipInsertion("2", 1, 0, dummy_3, true, false, true); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int dummy_3; bool res = timeline_0->requestClipInsertion("2", 1, 30, dummy_3, true, false, true); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int dummy_3; bool res = timeline_0->requestClipInsertion("2", 1, 60, dummy_3, true, false, true); REQUIRE(res == true); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); { int res = timeline_0->requestClipsGroup({3, 2}, true, GroupType::AVSplit); REQUIRE(res == -1); } REQUIRE(timeline_0->checkConsistency()); undoStack->undo(); REQUIRE(timeline_0->checkConsistency()); undoStack->redo(); REQUIRE(timeline_0->checkConsistency()); } pCore->m_projectManager = nullptr; + Logger::print_trace(); } diff --git a/tests/test_utils.hpp b/tests/test_utils.hpp index 9b3bf7af4..43134f705 100644 --- a/tests/test_utils.hpp +++ b/tests/test_utils.hpp @@ -1,85 +1,87 @@ #pragma once #include "bin/model/markerlistmodel.hpp" #include "catch.hpp" #include "doc/docundostack.hpp" #include #include #include #include +#include "logger.hpp" #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" #pragma GCC diagnostic push #include "fakeit.hpp" #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 "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" +#include "transitions/transitionsrepository.hpp" using namespace fakeit; #define RESET(mock) \ mock.Reset(); \ Fake(Method(mock, adjustAssetRange)); \ Spy(Method(mock, _resetView)); \ Spy(Method(mock, _beginInsertRows)); \ Spy(Method(mock, _beginRemoveRows)); \ Spy(Method(mock, _endInsertRows)); \ Spy(Method(mock, _endRemoveRows)); \ Spy(OverloadedMethod(mock, notifyChange, void(const QModelIndex &, const QModelIndex &, bool, bool, bool))); \ Spy(OverloadedMethod(mock, notifyChange, void(const QModelIndex &, const QModelIndex &, const QVector &))); \ Spy(OverloadedMethod(mock, notifyChange, void(const QModelIndex &, const QModelIndex &, int))); #define NO_OTHERS() \ VerifyNoOtherInvocations(Method(timMock, _beginRemoveRows)); \ VerifyNoOtherInvocations(Method(timMock, _beginInsertRows)); \ VerifyNoOtherInvocations(Method(timMock, _endRemoveRows)); \ VerifyNoOtherInvocations(Method(timMock, _endInsertRows)); \ VerifyNoOtherInvocations(OverloadedMethod(timMock, notifyChange, void(const QModelIndex &, const QModelIndex &, bool, bool, bool))); \ VerifyNoOtherInvocations(OverloadedMethod(timMock, notifyChange, void(const QModelIndex &, const QModelIndex &, const QVector &))); \ RESET(timMock); #define CHECK_MOVE(times) \ Verify(Method(timMock, _beginRemoveRows) + Method(timMock, _endRemoveRows) + Method(timMock, _beginInsertRows) + Method(timMock, _endInsertRows)) \ .Exactly(times); \ NO_OTHERS(); #define CHECK_INSERT(times) \ Verify(Method(timMock, _beginInsertRows) + Method(timMock, _endInsertRows)).Exactly(times); \ NO_OTHERS(); #define CHECK_REMOVE(times) \ Verify(Method(timMock, _beginRemoveRows) + Method(timMock, _endRemoveRows)).Exactly(times); \ NO_OTHERS(); #define CHECK_RESIZE(times) \ Verify(OverloadedMethod(timMock, notifyChange, void(const QModelIndex &, const QModelIndex &, bool, bool, bool))).Exactly(times); \ NO_OTHERS(); #define CHECK_UPDATE(role) \ Verify(OverloadedMethod(timMock, notifyChange, void(const QModelIndex &, const QModelIndex &, int)) \ .Matching([](const QModelIndex &, const QModelIndex &, int c) { return c == role; })) \ .Exactly(1); \ NO_OTHERS(); QString createProducer(Mlt::Profile &prof, std::string color, std::shared_ptr binModel, int length = 20, bool limited = true); QString createProducerWithSound(Mlt::Profile &prof, std::shared_ptr binModel); diff --git a/tests/timewarptest.cpp b/tests/timewarptest.cpp index 12be262c0..1cc48c9da 100644 --- a/tests/timewarptest.cpp +++ b/tests/timewarptest.cpp @@ -1,115 +1,117 @@ #include "test_utils.hpp" using namespace fakeit; Mlt::Profile profile_timewarp; TEST_CASE("Test of timewarping", "[Timewarp]") { + Logger::clear(); auto binModel = pCore->projectItemModel(); binModel->clean(); std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); // Here we do some trickery to enable testing. // We mock the project class so that the undoStack function returns our undoStack Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; // We also mock timeline object to spy few functions and mock others TimelineItemModel tim(&profile_timewarp, undoStack); Mock timMock(tim); auto timeline = std::shared_ptr(&timMock.get(), [](...) {}); TimelineItemModel::finishConstruct(timeline, guideModel); RESET(timMock); TimelineModel::next_id = 0; undoStack->undo(); undoStack->redo(); undoStack->redo(); undoStack->undo(); QString binId = createProducer(profile_timewarp, "red", binModel); QString binId2 = createProducer(profile_timewarp, "blue", binModel); QString binId3 = createProducerWithSound(profile_timewarp, binModel); int cid1 = ClipModel::construct(timeline, binId, -1, PlaylistState::VideoOnly); int tid1 = TrackModel::construct(timeline); int tid2 = TrackModel::construct(timeline); Q_UNUSED(tid1); Q_UNUSED(tid2); int cid2 = ClipModel::construct(timeline, binId2, -1, PlaylistState::VideoOnly); int cid3 = ClipModel::construct(timeline, binId3, -1, PlaylistState::VideoOnly); timeline->m_allClips[cid1]->m_endlessResize = false; timeline->m_allClips[cid2]->m_endlessResize = false; timeline->m_allClips[cid3]->m_endlessResize = false; SECTION("Timewarping orphan clip") { int originalDuration = timeline->getClipPlaytime(cid3); REQUIRE(timeline->checkConsistency()); REQUIRE(timeline->getClipTrackId(cid3) == -1); REQUIRE(timeline->getClipSpeed(cid3) == 1.); std::function undo = []() { return true; }; std::function redo = []() { return true; }; REQUIRE(timeline->requestClipTimeWarp(cid3, 0.1, undo, redo)); CHECK_UPDATE(TimelineModel::SpeedRole); REQUIRE(timeline->getClipSpeed(cid3) == 0.1); INFO(timeline->m_allClips[cid3]->getIn()); INFO(timeline->m_allClips[cid3]->getOut()); REQUIRE(timeline->getClipPlaytime(cid3) == originalDuration / 0.1); undo(); CHECK_UPDATE(TimelineModel::SpeedRole); REQUIRE(timeline->getClipSpeed(cid3) == 1.); REQUIRE(timeline->getClipPlaytime(cid3) == originalDuration); redo(); CHECK_UPDATE(TimelineModel::SpeedRole); REQUIRE(timeline->getClipSpeed(cid3) == 0.1); REQUIRE(timeline->getClipPlaytime(cid3) == originalDuration / 0.1); std::function undo2 = []() { return true; }; std::function redo2 = []() { return true; }; REQUIRE(timeline->requestClipTimeWarp(cid3, 1.2, undo2, redo2)); CHECK_UPDATE(TimelineModel::SpeedRole); REQUIRE(timeline->getClipSpeed(cid3) == 1.2); REQUIRE(timeline->getClipPlaytime(cid3) == int(originalDuration / 1.2)); undo2(); CHECK_UPDATE(TimelineModel::SpeedRole); REQUIRE(timeline->getClipSpeed(cid3) == 0.1); REQUIRE(timeline->getClipPlaytime(cid3) == originalDuration / 0.1); undo(); CHECK_UPDATE(TimelineModel::SpeedRole); REQUIRE(timeline->getClipSpeed(cid3) == 1.); REQUIRE(timeline->getClipPlaytime(cid3) == originalDuration); // Finally, we test that setting a very high speed isn't possible. // Specifically, it must be impossible to make the clip shorter than one frame int curLength = timeline->getClipPlaytime(cid3); // This is the limit, should work REQUIRE(timeline->requestClipTimeWarp(cid3, double(curLength), undo2, redo2)); CHECK_UPDATE(TimelineModel::SpeedRole); REQUIRE(timeline->getClipSpeed(cid3) == double(curLength)); REQUIRE(timeline->getClipPlaytime(cid3) == 1); // This is the higher than the limit, should not work REQUIRE_FALSE(timeline->requestClipTimeWarp(cid3, double(curLength) + 0.1, undo2, redo2)); } binModel->clean(); pCore->m_projectManager = nullptr; + Logger::print_trace(); }