class TestUndoableActionLog(TestCase):
    def setUp(self):
        self.log = UndoableActionLog()
        self._connectToUndoableActionLog(self.log)
        self.signals = []

    def tearDown(self):
        self._disconnectFromUndoableActionLog(self.log)

    def _undoActionLogSignalCb(self, log, *args):
        args = list(args)
        signalName = args.pop(-1)
        self.signals.append((signalName, args))

    def _connectToUndoableActionLog(self, log):
        for signalName in ("begin", "push", "rollback", "commit", "undo",
                           "redo"):
            log.connect(signalName, self._undoActionLogSignalCb, signalName)

    def _disconnectFromUndoableActionLog(self, log):
        self.log.disconnect_by_func(self._undoActionLogSignalCb)

    def testRollbackWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.rollback)

    def testCommitWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.commit)

    def testPushWrongState(self):
        # no error in this case
        self.log.push(None)

    def testUndoWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.undo)

    def testRedoWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.redo)

    def testCheckpoint(self):
        self.log.begin("meh")
        self.log.push(DummyUndoableAction())
        self.assertRaises(UndoWrongStateError, self.log.checkpoint)
        self.log.rollback()
        self.log.checkpoint()
        self.assertNotEqual(self.log._checkpoint, None)

    def testDirty(self):
        self.assertFalse(self.log.dirty())
        self.log.begin("meh")
        self.log.push(DummyUndoableAction())
        self.log.commit()
        self.assertTrue(self.log.dirty())
        self.log.checkpoint()
        self.assertFalse(self.log.dirty())
        self.log.undo()
        self.assertTrue(self.log.dirty())
        self.log.redo()
        self.assertFalse(self.log.dirty())

    def testCommit(self):
        """
        Commit a stack.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, nested) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertFalse(nested)

        self.assertEqual(self.log.undo_stacks, [])
        self.log.commit()
        self.assertEqual(len(self.signals), 2)
        name, (stack, nested) = self.signals[1]
        self.assertEqual(name, "commit")
        self.assertFalse(nested)
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def testNestedCommit(self):
        """
        Do two nested commits.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, nested) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertFalse(nested)

        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("nested")
        self.assertEqual(len(self.signals), 2)
        name, (stack, nested) = self.signals[1]
        self.assertEqual(name, "begin")
        self.assertTrue(nested)

        self.assertEqual(self.log.undo_stacks, [])
        self.log.commit()
        self.assertEqual(len(self.signals), 3)
        name, (stack, nested) = self.signals[2]
        self.assertEqual(name, "commit")
        self.assertTrue(nested)
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.assertEqual(self.log.undo_stacks, [])
        self.log.commit()
        self.assertEqual(len(self.signals), 4)
        name, (stack, nested) = self.signals[3]
        self.assertEqual(name, "commit")
        self.assertFalse(nested)
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def testRollback(self):
        """
        Test a rollback.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, nested) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertFalse(nested)

        self.log.rollback()
        self.assertEqual(len(self.signals), 2)
        name, (stack, nested) = self.signals[1]
        self.assertEqual(name, "rollback")
        self.assertFalse(nested)
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def testNestedRollback(self):
        """
        Test two nested rollbacks.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, nested) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertFalse(nested)

        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("nested")
        self.assertEqual(len(self.signals), 2)
        name, (stack, nested) = self.signals[1]
        self.assertEqual(name, "begin")
        self.assertTrue(nested)

        self.log.rollback()
        self.assertEqual(len(self.signals), 3)
        name, (stack, nested) = self.signals[2]
        self.assertEqual(name, "rollback")
        self.assertTrue(nested)
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.log.rollback()
        self.assertEqual(len(self.signals), 4)
        name, (stack, nested) = self.signals[3]
        self.assertEqual(name, "rollback")
        self.assertFalse(nested)
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def testUndoRedo(self):
        """
        Try an undo() redo() sequence.
        """
        # begin
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, nested) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertFalse(nested)

        # push two actions
        action1 = DummyUndoableAction()
        self.log.push(action1)
        self.assertEqual(len(self.signals), 2)
        name, (stack, signalAction) = self.signals[1]
        self.assertEqual(name, "push")
        self.assertTrue(action1 is signalAction)

        action2 = DummyUndoableAction()
        self.log.push(action2)
        self.assertEqual(len(self.signals), 3)
        name, (stack, signalAction) = self.signals[2]
        self.assertEqual(name, "push")
        self.assertTrue(action2 is signalAction)

        # commit
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.commit()
        self.assertEqual(len(self.signals), 4)
        name, (stack, nested) = self.signals[3]
        self.assertEqual(name, "commit")
        self.assertFalse(nested)
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.assertTrue(action1.done_)
        self.assertTrue(action2.done_)

        # undo what we just committed
        self.log.undo()
        self.assertEqual(len(self.signals), 5)
        name, stack = self.signals[4]
        self.assertEqual(name, "undo")
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 1)

        self.assertFalse(action1.done_)
        self.assertFalse(action2.done_)

        # redo
        self.log.redo()
        self.assertEqual(len(self.signals), 6)
        name, stack = self.signals[5]
        self.assertEqual(name, "redo")
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.assertTrue(action1.done_)
        self.assertTrue(action2.done_)

    def testOrder(self):
        """
        Test that actions are undone and redone in the correct order.
        """
        call_sequence = []

        class Action(UndoableAction):
            def __init__(self, n):
                UndoableAction.__init__(self)
                self.n = n

            def do(self):
                call_sequence.append("do%s" % self.n)
                self._done()

            def undo(self):
                call_sequence.append("undo%s" % self.n)
                self._undone()

        action1 = Action(1)
        action2 = Action(2)
        action3 = Action(3)

        self.log.begin("meh")
        self.log.push(action1)
        self.log.begin("nested")
        self.log.push(action2)
        self.log.commit()
        self.log.push(action3)
        self.log.commit()

        self.log.undo()
        self.assertEqual(call_sequence, ["undo3", "undo2", "undo1"])

        call_sequence[:] = []
        self.log.redo()
        self.assertEqual(call_sequence, ["do1", "do2", "do3"])

        call_sequence[:] = []
        self.log.undo()
        self.assertEqual(call_sequence, ["undo3", "undo2", "undo1"])
Exemple #2
0
class TestUndoableActionLog(TestCase):

    def setUp(self):
        self.log = UndoableActionLog()
        self._connectToUndoableActionLog(self.log)
        self.signals = []

    def tearDown(self):
        self._disconnectFromUndoableActionLog(self.log)

    def _undoActionLogSignalCb(self, log, *args):
        args = list(args)
        signalName = args.pop(-1)
        self.signals.append((signalName, args))

    def _connectToUndoableActionLog(self, log):
        for signalName in ("begin", "push", "rollback", "commit", "move"):
            log.connect(signalName, self._undoActionLogSignalCb, signalName)

    def _disconnectFromUndoableActionLog(self, log):
        self.log.disconnect_by_func(self._undoActionLogSignalCb)

    def testRollbackWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.rollback)

    def testCommitWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.commit, "")

    def testPushWrongState(self):
        # no error in this case
        self.log.push(None)

    def testUndoWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.undo)

    def testRedoWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.redo)

    def testCheckpoint(self):
        self.log.begin("meh")
        self.log.push(mock.Mock(spec=UndoableAction))
        self.assertRaises(UndoWrongStateError, self.log.checkpoint)
        self.log.rollback()
        self.log.checkpoint()
        self.assertNotEqual(self.log._checkpoint, None)

    def testDirty(self):
        self.assertFalse(self.log.dirty())
        self.log.begin("meh")
        self.log.push(mock.Mock(spec=UndoableAction))
        self.log.commit("meh")
        self.assertTrue(self.log.dirty())
        self.log.checkpoint()
        self.assertFalse(self.log.dirty())
        self.log.undo()
        self.assertTrue(self.log.dirty())
        self.log.redo()
        self.assertFalse(self.log.dirty())

    def testCommit(self):
        """
        Commit a stack.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack,) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.assertEqual(self.log.undo_stacks, [])
        self.log.push(mock.Mock(spec=UndoableAction))
        self.log.commit("meh")
        self.assertEqual(len(self.signals), 3)
        name, (stack, action) = self.signals[1]
        self.assertEqual(name, "push")
        name, (stack,) = self.signals[2]
        self.assertEqual(name, "commit")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def test_commit_proper(self):
        self.log.begin("meh")
        self.assertRaises(UndoWrongStateError, self.log.commit, "notmeh")

    def testNestedCommit(self):
        """
        Do two nested commits.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack,) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("nested")
        self.assertEqual(len(self.signals), 2)
        name, (stack,) = self.signals[1]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.assertEqual(self.log.undo_stacks, [])
        self.log.push(mock.Mock(spec=UndoableAction))
        self.log.commit("nested")
        self.assertEqual(len(self.signals), 4)
        name, (stack, action) = self.signals[2]
        self.assertEqual(name, "push")
        name, (stack,) = self.signals[3]
        self.assertEqual(name, "commit")
        self.assertTrue(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.assertEqual(self.log.undo_stacks, [])
        self.log.commit("meh")
        self.assertEqual(len(self.signals), 5)
        name, (stack,) = self.signals[4]
        self.assertEqual(name, "commit")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def test_finalizing_action(self):
        action1 = mock.Mock()
        action2 = mock.Mock()
        with self.log.started("one", finalizing_action=action1):
            self.log.push(mock.Mock(spec=UndoableAction))
            with self.log.started("two", finalizing_action=action2):
                self.log.push(mock.Mock(spec=UndoableAction))
        action1.do.assert_called_once_with()
        # For now, we call the finalizing action only for the top stack.
        action2.do.assert_not_called()

    def testRollback(self):
        """
        Test a rollback.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack,) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.log.rollback()
        self.assertEqual(len(self.signals), 2)
        name, (stack,) = self.signals[1]
        self.assertEqual(name, "rollback")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def testNestedRollback(self):
        """
        Test two nested rollbacks.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack,) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("nested")
        self.assertEqual(len(self.signals), 2)
        name, (stack,) = self.signals[1]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.log.rollback()
        self.assertEqual(len(self.signals), 3)
        name, (stack,) = self.signals[2]
        self.assertEqual(name, "rollback")
        self.assertTrue(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.log.rollback()
        self.assertEqual(len(self.signals), 4)
        name, (stack,) = self.signals[3]
        self.assertEqual(name, "rollback")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def testUndoRedo(self):
        """
        Try an undo() redo() sequence.
        """
        # begin
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack,) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        # push two actions
        action1 = mock.Mock(spec=UndoableAction)
        action1.expand.return_value = False
        self.log.push(action1)
        self.assertEqual(len(self.signals), 2)
        name, (stack, signalAction) = self.signals[1]
        self.assertEqual(name, "push")
        self.assertTrue(action1 is signalAction)

        action2 = mock.Mock(spec=UndoableAction)
        self.log.push(action2)
        self.assertEqual(len(self.signals), 3)
        name, (stack, signalAction) = self.signals[2]
        self.assertEqual(name, "push")
        self.assertTrue(action2 is signalAction)

        # commit
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.commit("meh")
        self.assertEqual(len(self.signals), 4)
        name, (stack,) = self.signals[3]
        self.assertEqual(name, "commit")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.assertEqual(action1.do.call_count, 0)
        self.assertEqual(action1.undo.call_count, 0)
        self.assertEqual(action2.do.call_count, 0)
        self.assertEqual(action2.undo.call_count, 0)

        # undo what we just committed
        self.log.undo()
        self.assertEqual(len(self.signals), 5)
        name, stack = self.signals[4]
        self.assertEqual(name, "move")
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 1)
        self.assertEqual(action1.do.call_count, 0)
        self.assertEqual(action1.undo.call_count, 1)
        self.assertEqual(action2.do.call_count, 0)
        self.assertEqual(action2.undo.call_count, 1)

        # redo
        self.log.redo()
        self.assertEqual(len(self.signals), 6)
        name, stack = self.signals[5]
        self.assertEqual(name, "move")
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.assertEqual(action1.do.call_count, 1)
        self.assertEqual(action1.undo.call_count, 1)
        self.assertEqual(action2.do.call_count, 1)
        self.assertEqual(action2.undo.call_count, 1)

    def testOrder(self):
        """
        Test that actions are undone and redone in the correct order.
        """
        order = mock.Mock()
        order.action1 = mock.Mock(spec=UndoableAction)
        order.action1.expand.return_value = False
        order.action2 = mock.Mock(spec=UndoableAction)
        order.action2.expand.return_value = False
        order.action3 = mock.Mock(spec=UndoableAction)
        order.action3.expand.return_value = False

        with self.log.started("meh"):
            self.log.push(order.action1)
            with self.log.started("nested"):
                self.log.push(order.action2)
            self.log.push(order.action3)

        self.log.undo()
        order.assert_has_calls([mock.call.action3.undo(),
                                mock.call.action2.undo(),
                                mock.call.action1.undo()])

        self.log.redo()
        order.assert_has_calls([mock.call.action1.do(),
                                mock.call.action2.do(),
                                mock.call.action3.do()])

        self.log.undo()
        order.assert_has_calls([mock.call.action3.undo(),
                                mock.call.action2.undo(),
                                mock.call.action1.undo()])

    def test_toplevel_operation(self):
        """Checks the toplevel operations nesting."""
        self.log.begin("one", toplevel=False)
        self.log.commit("one")

        self.log.begin("two", toplevel=True)
        self.log.commit("two")

        self.log.begin("three")
        self.assertRaises(UndoWrongStateError,
                          self.log.begin,
                          "four", toplevel=True)
        self.log.begin("nested1")
        self.log.begin("nested2", toplevel=False)
Exemple #3
0
class TestUndoableActionLog(common.TestCase):
    """Tests for the UndoableActionLog class."""

    def setUp(self):
        self.log = UndoableActionLog()
        self._connect_to_undoable_action_log(self.log)
        self.signals = []

    def tearDown(self):
        self._disconnect_from_undoable_action_log()

    def check_signals(self, *expected_signals):
        signals = [item[0] for item in self.signals]
        self.assertListEqual(signals, list(expected_signals))

    def _undo_action_log_signal_cb(self, log, *args):
        args = list(args)
        signal_name = args.pop(-1)
        self.signals.append((signal_name, args))

    def _connect_to_undoable_action_log(self, log):
        for signal_name in ("begin", "push", "rollback", "commit", "move"):
            log.connect(signal_name, self._undo_action_log_signal_cb, signal_name)

    def _disconnect_from_undoable_action_log(self):
        self.log.disconnect_by_func(self._undo_action_log_signal_cb)

    def test_rollback_wrong_state(self):
        self.assertRaises(UndoWrongStateError, self.log.rollback)

    def test_commit_wrong_state(self):
        self.assertRaises(UndoWrongStateError, self.log.commit, "")

    def test_push_wrong_state(self):
        # no error in this case
        self.log.push(None)

    def test_undo_wrong_state(self):
        self.assertRaises(UndoWrongStateError, self.log.undo)

    def test_redo_wrong_state(self):
        self.assertRaises(UndoWrongStateError, self.log.redo)

    def test_checkpoint(self):
        self.log.begin("meh")
        self.log.push(mock.Mock(spec=UndoableAction))
        self.assertRaises(UndoWrongStateError, self.log.checkpoint)
        self.log.rollback()
        self.log.checkpoint()
        self.assertNotEqual(self.log._checkpoint, None)

    def test_dirty(self):
        self.assertFalse(self.log.dirty())
        self.log.begin("meh")
        self.log.push(mock.Mock(spec=UndoableAction))
        self.log.commit("meh")
        self.assertTrue(self.log.dirty())
        self.log.checkpoint()
        self.assertFalse(self.log.dirty())
        self.log.undo()
        self.assertTrue(self.log.dirty())
        self.log.redo()
        self.assertFalse(self.log.dirty())

    def test_commit(self):
        """Checks committing a stack."""
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (_stack,) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.assertEqual(self.log.undo_stacks, [])
        self.log.push(mock.Mock(spec=UndoableAction))
        self.log.commit("meh")
        self.assertEqual(len(self.signals), 3)
        name, (_stack, _action) = self.signals[1]
        self.assertEqual(name, "push")
        name, (_stack,) = self.signals[2]
        self.assertEqual(name, "commit")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def test_commit_proper(self):
        self.log.begin("meh")
        self.assertRaises(UndoWrongStateError, self.log.commit, "notmeh")

    def test_nested_commit(self):
        """Checks two nested commits."""
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (_stack,) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("nested")
        self.assertEqual(len(self.signals), 2)
        name, (_stack,) = self.signals[1]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.assertEqual(self.log.undo_stacks, [])
        self.log.push(mock.Mock(spec=UndoableAction))
        self.log.commit("nested")
        self.assertEqual(len(self.signals), 4)
        name, (_stack, _action) = self.signals[2]
        self.assertEqual(name, "push")
        name, (_stack,) = self.signals[3]
        self.assertEqual(name, "commit")
        self.assertTrue(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.assertEqual(self.log.undo_stacks, [])
        self.log.commit("meh")
        self.assertEqual(len(self.signals), 5)
        name, (_stack,) = self.signals[4]
        self.assertEqual(name, "commit")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def test_finalizing_action(self):
        action1 = mock.Mock()
        action2 = mock.Mock()
        with self.log.started("one", finalizing_action=action1):
            self.log.push(mock.Mock(spec=UndoableAction))
            with self.log.started("two", finalizing_action=action2):
                self.log.push(mock.Mock(spec=UndoableAction))
        action1.do.assert_called_once_with()
        # For now, we call the finalizing action only for the top stack.
        action2.do.assert_not_called()

    def test_rollback(self):
        """Checks a rollback."""
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.check_signals("begin")
        name, (_stack,) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        action = mock.Mock(spec=UndoableAction)
        self.log.push(action)

        self.log.rollback()

        action.undo.assert_called_once_with()

        self.check_signals("begin", "push", "rollback")
        name, (_stack,) = self.signals[2]
        self.assertEqual(name, "rollback")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def test_rollback_noop(self):
        """Checks a rollback which does not act."""
        self.log.begin("meh")

        action = mock.Mock(spec=UndoableAction)
        self.log.push(action)

        self.log.rollback(undo=False)
        action.undo.assert_not_called()

    def test_nested_rollback(self):
        """Checks two nested rollbacks."""
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (_stack,) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("nested")
        self.assertEqual(len(self.signals), 2)
        name, (_stack,) = self.signals[1]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.log.rollback()
        self.assertEqual(len(self.signals), 3)
        name, (_stack,) = self.signals[2]
        self.assertEqual(name, "rollback")
        self.assertTrue(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.log.rollback()
        self.assertEqual(len(self.signals), 4)
        name, (_stack,) = self.signals[3]
        self.assertEqual(name, "rollback")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def test_undo_redo(self):
        """Tries an undo() redo() sequence."""
        # begin
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (_stack,) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        # push two actions
        action1 = mock.Mock(spec=UndoableAction)
        action1.expand.return_value = False
        self.log.push(action1)
        self.assertEqual(len(self.signals), 2)
        name, (_stack, signal_action) = self.signals[1]
        self.assertEqual(name, "push")
        self.assertTrue(action1 is signal_action)

        action2 = mock.Mock(spec=UndoableAction)
        self.log.push(action2)
        self.assertEqual(len(self.signals), 3)
        name, (_stack, signal_action) = self.signals[2]
        self.assertEqual(name, "push")
        self.assertTrue(action2 is signal_action)

        # commit
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.commit("meh")
        self.assertEqual(len(self.signals), 4)
        name, (_stack,) = self.signals[3]
        self.assertEqual(name, "commit")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.assertEqual(action1.do.call_count, 0)
        self.assertEqual(action1.undo.call_count, 0)
        self.assertEqual(action2.do.call_count, 0)
        self.assertEqual(action2.undo.call_count, 0)

        # undo what we just committed
        self.log.undo()
        self.assertEqual(len(self.signals), 5)
        name, _args = self.signals[4]
        self.assertEqual(name, "move")
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 1)
        self.assertEqual(action1.do.call_count, 0)
        self.assertEqual(action1.undo.call_count, 1)
        self.assertEqual(action2.do.call_count, 0)
        self.assertEqual(action2.undo.call_count, 1)

        # redo
        self.log.redo()
        self.assertEqual(len(self.signals), 6)
        name, _args = self.signals[5]
        self.assertEqual(name, "move")
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.assertEqual(action1.do.call_count, 1)
        self.assertEqual(action1.undo.call_count, 1)
        self.assertEqual(action2.do.call_count, 1)
        self.assertEqual(action2.undo.call_count, 1)

    def test_order(self):
        """Checks actions are undone and redone in the correct order."""
        order = mock.Mock()
        order.action1 = mock.Mock(spec=UndoableAction)
        order.action1.expand.return_value = False
        order.action2 = mock.Mock(spec=UndoableAction)
        order.action2.expand.return_value = False
        order.action3 = mock.Mock(spec=UndoableAction)
        order.action3.expand.return_value = False

        with self.log.started("meh"):
            self.log.push(order.action1)
            with self.log.started("nested"):
                self.log.push(order.action2)
            self.log.push(order.action3)

        self.log.undo()
        order.assert_has_calls([mock.call.action3.undo(),
                                mock.call.action2.undo(),
                                mock.call.action1.undo()])

        self.log.redo()
        order.assert_has_calls([mock.call.action1.do(),
                                mock.call.action2.do(),
                                mock.call.action3.do()])

        self.log.undo()
        order.assert_has_calls([mock.call.action3.undo(),
                                mock.call.action2.undo(),
                                mock.call.action1.undo()])

    def test_toplevel_operation(self):
        """Checks the toplevel operations nesting."""
        self.log.begin("one", toplevel=False)
        self.log.commit("one")

        self.log.begin("two", toplevel=True)
        self.log.commit("two")

        self.log.begin("three")
        self.assertRaises(UndoWrongStateError,
                          self.log.begin,
                          "four", toplevel=True)
        self.log.begin("nested1")
        self.log.begin("nested2", toplevel=False)

    def test_failing_operation_rollback(self):
        """Checks that failing operations are rolled back."""
        action = mock.Mock(spec=UndoableAction)

        class WatchingError(Exception):
            pass

        with self.assertRaises(WatchingError):
            with self.log.started("failing_op"):
                self.log.push(action)
                raise WatchingError()

        # Check the rollback happened
        self.assertEqual(action.do.call_count, 0)
        self.assertEqual(action.undo.call_count, 1)
        # Check the undo and redo stacks are empty
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def test_merging(self):
        with self.log.started("one", mergeable=False):
            action = mock.Mock(spec=UndoableAction)
            action.expand.side_effect = Exception("should not have been called")
            self.log.push(action)
        self.assertEqual(len(self.log.undo_stacks), 1)

        with self.log.started("one", mergeable=True):
            action = mock.Mock(spec=UndoableAction)
            action.expand.return_value = False
            self.log.push(action)
        self.assertEqual(len(self.log.undo_stacks), 2)

        with self.log.started("one", mergeable=True):
            action = mock.Mock(spec=UndoableAction)
            action.expand.return_value = True
            self.log.push(action)
        self.assertEqual(len(self.log.undo_stacks), 3)

        with self.log.started("one", mergeable=True):
            action = mock.Mock(spec=UndoableAction)
            action.expand.return_value = True
            self.log.push(action)
        self.assertEqual(len(self.log.undo_stacks), 3)

        with self.log.started("one", mergeable=False):
            action = mock.Mock(spec=UndoableAction)
            action.expand.side_effect = Exception("should not have been called")
            self.log.push(action)
        self.assertEqual(len(self.log.undo_stacks), 4)
Exemple #4
0
class TestTimelineUndo(TestCase):

    def setUp(self):
        app = Pitivi()
        app._startupCb(app)
        app.project_manager.newBlankProject()

        self.timeline = app.project_manager.current_project.timeline
        self.layer = GES.Layer()
        self.timeline.add_layer(self.layer)
        self.action_log = UndoableActionLog()
        self.observer = TimelineLogObserverSpy(self.action_log)
        self.observer.startObserving(self.timeline)

    def getTimelineClips(self):
        for layer in self.timeline.layers:
            for clip in layer.get_clips():
                yield clip

    @staticmethod
    def commitCb(action_log, stack, nested, stacks):
        stacks.append(stack)

    def testAddClip(self):
        stacks = []
        self.action_log.connect("commit", TestTimelineUndo.commitCb, stacks)

        clip1 = GES.TitleClip()
        self.action_log.begin("add clip")
        self.layer.add_clip(clip1)
        self.action_log.commit()

        self.assertEqual(1, len(stacks))
        stack = stacks[0]
        self.assertEqual(1, len(stack.done_actions))
        action = stack.done_actions[0]
        self.assertTrue(isinstance(action, ClipAdded))
        self.assertTrue(clip1 in self.getTimelineClips())

        self.action_log.undo()
        self.assertFalse(clip1 in self.getTimelineClips())

        self.action_log.redo()
        self.assertTrue(clip1 in self.getTimelineClips())

    def testRemoveClip(self):
        stacks = []
        self.action_log.connect("commit", TestTimelineUndo.commitCb, stacks)

        clip1 = GES.TitleClip()
        self.layer.add_clip(clip1)
        self.action_log.begin("remove clip")
        self.layer.remove_clip(clip1)
        self.action_log.commit()

        self.assertEqual(1, len(stacks))
        stack = stacks[0]
        self.assertEqual(1, len(stack.done_actions))
        action = stack.done_actions[0]
        self.assertTrue(isinstance(action, ClipRemoved))
        self.assertFalse(clip1 in self.getTimelineClips())

        self.action_log.undo()
        self.assertTrue(clip1 in self.getTimelineClips())

        self.action_log.redo()
        self.assertFalse(clip1 in self.getTimelineClips())

    def testAddEffectToClip(self):
        stacks = []
        self.action_log.connect("commit", TestTimelineUndo.commitCb, stacks)

        clip1 = GES.TitleClip()
        self.layer.add_clip(clip1)

        effect1 = GES.Effect.new("agingtv")
        self.action_log.begin("add effect")
        clip1.add(effect1)
        self.action_log.commit()

        self.assertEqual(1, len(stacks))
        stack = stacks[0]
        self.assertEqual(1, len(stack.done_actions), stack.done_actions)
        action = stack.done_actions[0]
        self.assertTrue(isinstance(action, TrackElementAdded))

        self.assertTrue(effect1 in clip1.get_children(True))
        self.assertEqual(1, len([effect for effect in
                                 clip1.get_children(True)
                                 if isinstance(effect, GES.Effect)]))

        self.action_log.undo()
        self.assertFalse(effect1 in clip1.get_children(True))

        self.action_log.redo()
        self.assertEqual(1, len([effect for effect in
                                 clip1.get_children(True)
                                 if isinstance(effect, GES.Effect)]))

    def testRemoveEffectFromClip(self):
        stacks = []
        self.action_log.connect("commit", TestTimelineUndo.commitCb, stacks)

        clip1 = GES.TitleClip()
        self.layer.add_clip(clip1)

        effect1 = GES.Effect.new("agingtv")
        self.action_log.begin("add effect")
        clip1.add(effect1)
        self.action_log.commit()

        self.assertEqual(1, len(stacks))
        stack = stacks[0]
        self.assertEqual(1, len(stack.done_actions), stack.done_actions)
        action = stack.done_actions[0]
        self.assertTrue(isinstance(action, TrackElementAdded))

        self.assertTrue(effect1 in clip1.get_children(True))
        self.assertEqual(1, len([effect for effect in
                                 clip1.get_children(True)
                                 if isinstance(effect, GES.Effect)]))

        self.action_log.begin("remove effect")
        clip1.remove(effect1)
        self.action_log.commit()

        self.assertEqual(0, len([effect for effect in
                                 clip1.get_children(True)
                                 if isinstance(effect, GES.Effect)]))

        self.action_log.undo()
        self.assertEqual(1, len([effect for effect in
                                 clip1.get_children(True)
                                 if isinstance(effect, GES.Effect)]))

        self.action_log.redo()
        self.assertEqual(0, len([effect for effect in
                                 clip1.get_children(True)
                                 if isinstance(effect, GES.Effect)]))

    def testChangeEffectProperty(self):
        stacks = []
        self.action_log.connect("commit", TestTimelineUndo.commitCb, stacks)

        clip1 = GES.TitleClip()
        self.layer.add_clip(clip1)

        effect1 = GES.Effect.new("agingtv")
        self.action_log.begin("add effect")
        clip1.add(effect1)
        self.action_log.commit()

        self.assertEqual(1, len(stacks))
        stack = stacks[0]
        self.assertEqual(1, len(stack.done_actions), stack.done_actions)
        action = stack.done_actions[0]
        self.assertTrue(isinstance(action, TrackElementAdded))

        self.assertTrue(effect1 in clip1.get_children(True))
        self.assertEqual(1, len([effect for effect in
                                 clip1.get_children(True)
                                 if isinstance(effect, GES.Effect)]))

        self.action_log.begin("change child property")
        effect1.set_child_property("scratch-lines", 0)
        self.action_log.commit()

        self.assertEqual(effect1.get_child_property("scratch-lines")[1], 0)
        self.action_log.undo()
        self.assertEqual(effect1.get_child_property("scratch-lines")[1], 7)
        self.action_log.redo()
        self.assertEqual(effect1.get_child_property("scratch-lines")[1], 0)
        self.action_log.undo()
        self.assertTrue(effect1 in clip1.get_children(True))
        self.action_log.undo()
        self.assertFalse(effect1 in clip1.get_children(True))

    def testClipPropertyChange(self):
        stacks = []
        self.action_log.connect("commit", TestTimelineUndo.commitCb, stacks)

        clip1 = GES.TitleClip()
        self.layer.add_clip(clip1)
        clip1.set_start(5 * Gst.SECOND)
        clip1.set_duration(20 * Gst.SECOND)
        self.layer.add_clip(clip1)
        self.action_log.begin("modify clip")
        clip1.set_start(10 * Gst.SECOND)
        self.action_log.commit()

        self.assertEqual(1, len(stacks))
        stack = stacks[0]
        self.assertEqual(1, len(stack.done_actions))
        action = stack.done_actions[0]
        self.assertTrue(isinstance(action, ClipPropertyChanged))
        self.assertEqual(10 * Gst.SECOND, clip1.get_start())

        self.action_log.undo()
        self.assertEqual(5 * Gst.SECOND, clip1.get_start())
        self.action_log.redo()
        self.assertEqual(10 * Gst.SECOND, clip1.get_start())

        clip1.set_priority(10)
        self.action_log.begin("priority change")
        clip1.set_priority(20)
        self.action_log.commit()

        self.assertEqual(20, clip1.get_priority())
        self.action_log.undo()
        self.assertEqual(10, clip1.get_priority())
        self.action_log.redo()
        self.assertEqual(20, clip1.get_priority())

    def testUngroup(self):
        uri = common.getSampleUri("tears_of_steel.webm")
        asset = GES.UriClipAsset.request_sync(uri)
        clip1 = asset.extract()
        self.layer.add_clip(clip1)

        clip1.set_start(5 * Gst.SECOND)
        clip1.set_duration(0.5 * Gst.SECOND)
        timeline_clips = list(self.getTimelineClips())
        self.assertEqual(1, len(timeline_clips), timeline_clips)
        self.assertEqual(5 * Gst.SECOND, timeline_clips[0].get_start())
        self.assertEqual(0.5 * Gst.SECOND, timeline_clips[0].get_duration())

        self.action_log.begin("ungroup")
        ungrouped = GES.Container.ungroup(clip1, False)
        self.assertEqual(2, len(ungrouped), ungrouped)
        self.action_log.commit()
        timeline_clips = list(self.getTimelineClips())
        self.assertEqual(2, len(timeline_clips), timeline_clips)
        self.assertEqual(5 * Gst.SECOND, timeline_clips[0].get_start())
        self.assertEqual(0.5 * Gst.SECOND, timeline_clips[0].get_duration())
        self.assertEqual(5 * Gst.SECOND, timeline_clips[1].get_start())
        self.assertEqual(0.5 * Gst.SECOND, timeline_clips[1].get_duration())

        self.action_log.undo()
        timeline_clips = list(self.getTimelineClips())
        self.assertEqual(1, len(timeline_clips))
        self.assertEqual(5 * Gst.SECOND, timeline_clips[0].get_start())
        self.assertEqual(0.5 * Gst.SECOND, timeline_clips[0].get_duration())

    def testSplitClip(self):
        clip = GES.TitleClip()
        clip.set_start(0 * Gst.SECOND)
        clip.set_duration(20 * Gst.SECOND)

        self.layer.add_clip(clip)

        self.action_log.begin("split clip")
        clip1 = clip.split(10 * Gst.SECOND)
        self.assertEqual(2, len(self.layer.get_clips()))
        self.action_log.commit()

        self.action_log.begin("split clip")
        clip2 = clip1.split(15 * Gst.SECOND)
        self.assertEqual(3, len(self.layer.get_clips()))
        self.action_log.commit()

        self.action_log.undo()
        self.assertEqual(2, len(self.layer.get_clips()))
        self.action_log.undo()
        self.assertEqual(1, len(self.layer.get_clips()))

        self.action_log.redo()
        self.assertEqual(2, len(self.layer.get_clips()))
        self.action_log.redo()
        self.assertEqual(3, len(self.layer.get_clips()))
Exemple #5
0
class TestUndoableActionLog(TestCase):
    def setUp(self):
        self.log = UndoableActionLog()
        self._connectToUndoableActionLog(self.log)
        self.signals = []

    def tearDown(self):
        self._disconnectFromUndoableActionLog(self.log)

    def _undoActionLogSignalCb(self, log, *args):
        args = list(args)
        signalName = args.pop(-1)
        self.signals.append((signalName, args))

    def _connectToUndoableActionLog(self, log):
        for signalName in ("begin", "push", "rollback", "commit", "move"):
            log.connect(signalName, self._undoActionLogSignalCb, signalName)

    def _disconnectFromUndoableActionLog(self, log):
        self.log.disconnect_by_func(self._undoActionLogSignalCb)

    def testRollbackWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.rollback)

    def testCommitWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.commit, "")

    def testPushWrongState(self):
        # no error in this case
        self.log.push(None)

    def testUndoWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.undo)

    def testRedoWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.redo)

    def testCheckpoint(self):
        self.log.begin("meh")
        self.log.push(mock.Mock(spec=UndoableAction))
        self.assertRaises(UndoWrongStateError, self.log.checkpoint)
        self.log.rollback()
        self.log.checkpoint()
        self.assertNotEqual(self.log._checkpoint, None)

    def testDirty(self):
        self.assertFalse(self.log.dirty())
        self.log.begin("meh")
        self.log.push(mock.Mock(spec=UndoableAction))
        self.log.commit("meh")
        self.assertTrue(self.log.dirty())
        self.log.checkpoint()
        self.assertFalse(self.log.dirty())
        self.log.undo()
        self.assertTrue(self.log.dirty())
        self.log.redo()
        self.assertFalse(self.log.dirty())

    def testCommit(self):
        """
        Commit a stack.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, ) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.assertEqual(self.log.undo_stacks, [])
        self.log.push(mock.Mock())
        self.log.commit("meh")
        self.assertEqual(len(self.signals), 3)
        name, (stack, action) = self.signals[1]
        self.assertEqual(name, "push")
        name, (stack, ) = self.signals[2]
        self.assertEqual(name, "commit")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def test_commit_proper(self):
        self.log.begin("meh")
        self.assertRaises(UndoWrongStateError, self.log.commit, "notmeh")

    def testNestedCommit(self):
        """
        Do two nested commits.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, ) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("nested")
        self.assertEqual(len(self.signals), 2)
        name, (stack, ) = self.signals[1]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.assertEqual(self.log.undo_stacks, [])
        self.log.push(mock.Mock())
        self.log.commit("nested")
        self.assertEqual(len(self.signals), 4)
        name, (stack, action) = self.signals[2]
        self.assertEqual(name, "push")
        name, (stack, ) = self.signals[3]
        self.assertEqual(name, "commit")
        self.assertTrue(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.assertEqual(self.log.undo_stacks, [])
        self.log.commit("meh")
        self.assertEqual(len(self.signals), 5)
        name, (stack, ) = self.signals[4]
        self.assertEqual(name, "commit")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def test_finalizing_action(self):
        action1 = mock.Mock()
        action2 = mock.Mock()
        with self.log.started("one", finalizing_action=action1):
            self.log.push(mock.Mock())
            with self.log.started("two", finalizing_action=action2):
                self.log.push(mock.Mock())
        action1.do.assert_called_once_with()
        # For now, we call the finalizing action only for the top stack.
        action2.do.assert_not_called()

    def testRollback(self):
        """
        Test a rollback.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, ) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.log.rollback()
        self.assertEqual(len(self.signals), 2)
        name, (stack, ) = self.signals[1]
        self.assertEqual(name, "rollback")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def testNestedRollback(self):
        """
        Test two nested rollbacks.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, ) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("nested")
        self.assertEqual(len(self.signals), 2)
        name, (stack, ) = self.signals[1]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        self.log.rollback()
        self.assertEqual(len(self.signals), 3)
        name, (stack, ) = self.signals[2]
        self.assertEqual(name, "rollback")
        self.assertTrue(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.log.rollback()
        self.assertEqual(len(self.signals), 4)
        name, (stack, ) = self.signals[3]
        self.assertEqual(name, "rollback")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def testUndoRedo(self):
        """
        Try an undo() redo() sequence.
        """
        # begin
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, ) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertTrue(self.log.is_in_transaction())

        # push two actions
        action1 = mock.Mock(spec=UndoableAction)
        self.log.push(action1)
        self.assertEqual(len(self.signals), 2)
        name, (stack, signalAction) = self.signals[1]
        self.assertEqual(name, "push")
        self.assertTrue(action1 is signalAction)

        action2 = mock.Mock(spec=UndoableAction)
        self.log.push(action2)
        self.assertEqual(len(self.signals), 3)
        name, (stack, signalAction) = self.signals[2]
        self.assertEqual(name, "push")
        self.assertTrue(action2 is signalAction)

        # commit
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.commit("meh")
        self.assertEqual(len(self.signals), 4)
        name, (stack, ) = self.signals[3]
        self.assertEqual(name, "commit")
        self.assertFalse(self.log.is_in_transaction())
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.assertEqual(action1.do.call_count, 0)
        self.assertEqual(action1.undo.call_count, 0)
        self.assertEqual(action2.do.call_count, 0)
        self.assertEqual(action2.undo.call_count, 0)

        # undo what we just committed
        self.log.undo()
        self.assertEqual(len(self.signals), 5)
        name, stack = self.signals[4]
        self.assertEqual(name, "move")
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 1)
        self.assertEqual(action1.do.call_count, 0)
        self.assertEqual(action1.undo.call_count, 1)
        self.assertEqual(action2.do.call_count, 0)
        self.assertEqual(action2.undo.call_count, 1)

        # redo
        self.log.redo()
        self.assertEqual(len(self.signals), 6)
        name, stack = self.signals[5]
        self.assertEqual(name, "move")
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.assertEqual(action1.do.call_count, 1)
        self.assertEqual(action1.undo.call_count, 1)
        self.assertEqual(action2.do.call_count, 1)
        self.assertEqual(action2.undo.call_count, 1)

    def testOrder(self):
        """
        Test that actions are undone and redone in the correct order.
        """
        order = mock.Mock()
        order.action1 = mock.Mock(spec=UndoableAction)
        order.action2 = mock.Mock(spec=UndoableAction)
        order.action3 = mock.Mock(spec=UndoableAction)

        with self.log.started("meh"):
            self.log.push(order.action1)
            with self.log.started("nested"):
                self.log.push(order.action2)
            self.log.push(order.action3)

        self.log.undo()
        order.assert_has_calls([
            mock.call.action3.undo(),
            mock.call.action2.undo(),
            mock.call.action1.undo()
        ])

        self.log.redo()
        order.assert_has_calls([
            mock.call.action1.do(),
            mock.call.action2.do(),
            mock.call.action3.do()
        ])

        self.log.undo()
        order.assert_has_calls([
            mock.call.action3.undo(),
            mock.call.action2.undo(),
            mock.call.action1.undo()
        ])
Exemple #6
0
class TestUndoableActionLog(TestCase):
    def setUp(self):
        self.log = UndoableActionLog()
        self._connectToUndoableActionLog(self.log)
        self.signals = []

    def tearDown(self):
        self._disconnectFromUndoableActionLog(self.log)

    def _undoActionLogSignalCb(self, log, *args):
        args = list(args)
        signalName = args.pop(-1)
        self.signals.append((signalName, args))

    def _connectToUndoableActionLog(self, log):
        for signalName in ("begin", "push", "rollback", "commit",
                    "undo", "redo"):
            log.connect(signalName, self._undoActionLogSignalCb, signalName)

    def _disconnectFromUndoableActionLog(self, log):
        self.log.disconnect_by_func(self._undoActionLogSignalCb)

    def testRollbackWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.rollback)

    def testCommitWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.commit)

    def testPushWrongState(self):
        # no error in this case
        self.log.push(None)

    def testUndoWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.undo)

    def testRedoWrongState(self):
        self.assertRaises(UndoWrongStateError, self.log.redo)

    def testCheckpoint(self):
        self.log.begin("meh")
        self.log.push(DummyUndoableAction())
        self.assertRaises(UndoWrongStateError, self.log.checkpoint)
        self.log.rollback()
        self.log.checkpoint()
        self.assertNotEqual(self.log._checkpoint, None)

    def testDirty(self):
        self.assertFalse(self.log.dirty())
        self.log.begin("meh")
        self.log.push(DummyUndoableAction())
        self.log.commit()
        self.assertTrue(self.log.dirty())
        self.log.checkpoint()
        self.assertFalse(self.log.dirty())
        self.log.undo()
        self.assertTrue(self.log.dirty())
        self.log.redo()
        self.assertFalse(self.log.dirty())

    def testCommit(self):
        """
        Commit a stack.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, nested) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertFalse(nested)

        self.assertEqual(self.log.undo_stacks, [])
        self.log.commit()
        self.assertEqual(len(self.signals), 2)
        name, (stack, nested) = self.signals[1]
        self.assertEqual(name, "commit")
        self.assertFalse(nested)
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def testNestedCommit(self):
        """
        Do two nested commits.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, nested) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertFalse(nested)

        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("nested")
        self.assertEqual(len(self.signals), 2)
        name, (stack, nested) = self.signals[1]
        self.assertEqual(name, "begin")
        self.assertTrue(nested)

        self.assertEqual(self.log.undo_stacks, [])
        self.log.commit()
        self.assertEqual(len(self.signals), 3)
        name, (stack, nested) = self.signals[2]
        self.assertEqual(name, "commit")
        self.assertTrue(nested)
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.assertEqual(self.log.undo_stacks, [])
        self.log.commit()
        self.assertEqual(len(self.signals), 4)
        name, (stack, nested) = self.signals[3]
        self.assertEqual(name, "commit")
        self.assertFalse(nested)
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def testRollback(self):
        """
        Test a rollback.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, nested) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertFalse(nested)

        self.log.rollback()
        self.assertEqual(len(self.signals), 2)
        name, (stack, nested) = self.signals[1]
        self.assertEqual(name, "rollback")
        self.assertFalse(nested)
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def testNestedRollback(self):
        """
        Test two nested rollbacks.
        """
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, nested) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertFalse(nested)

        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.begin("nested")
        self.assertEqual(len(self.signals), 2)
        name, (stack, nested) = self.signals[1]
        self.assertEqual(name, "begin")
        self.assertTrue(nested)

        self.log.rollback()
        self.assertEqual(len(self.signals), 3)
        name, (stack, nested) = self.signals[2]
        self.assertEqual(name, "rollback")
        self.assertTrue(nested)
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.log.rollback()
        self.assertEqual(len(self.signals), 4)
        name, (stack, nested) = self.signals[3]
        self.assertEqual(name, "rollback")
        self.assertFalse(nested)
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)

    def testUndoRedo(self):
        """
        Try an undo() redo() sequence.
        """
        # begin
        self.log.begin("meh")
        self.assertEqual(len(self.signals), 1)
        name, (stack, nested) = self.signals[0]
        self.assertEqual(name, "begin")
        self.assertFalse(nested)

        # push two actions
        action1 = DummyUndoableAction()
        self.log.push(action1)
        self.assertEqual(len(self.signals), 2)
        name, (stack, signalAction) = self.signals[1]
        self.assertEqual(name, "push")
        self.assertTrue(action1 is signalAction)

        action2 = DummyUndoableAction()
        self.log.push(action2)
        self.assertEqual(len(self.signals), 3)
        name, (stack, signalAction) = self.signals[2]
        self.assertEqual(name, "push")
        self.assertTrue(action2 is signalAction)

        # commit
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 0)
        self.log.commit()
        self.assertEqual(len(self.signals), 4)
        name, (stack, nested) = self.signals[3]
        self.assertEqual(name, "commit")
        self.assertFalse(nested)
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.assertTrue(action1.done_)
        self.assertTrue(action2.done_)

        # undo what we just committed
        self.log.undo()
        self.assertEqual(len(self.signals), 5)
        name, stack = self.signals[4]
        self.assertEqual(name, "undo")
        self.assertEqual(len(self.log.undo_stacks), 0)
        self.assertEqual(len(self.log.redo_stacks), 1)

        self.assertFalse(action1.done_)
        self.assertFalse(action2.done_)

        # redo
        self.log.redo()
        self.assertEqual(len(self.signals), 6)
        name, stack = self.signals[5]
        self.assertEqual(name, "redo")
        self.assertEqual(len(self.log.undo_stacks), 1)
        self.assertEqual(len(self.log.redo_stacks), 0)

        self.assertTrue(action1.done_)
        self.assertTrue(action2.done_)

    def testOrder(self):
        """
        Test that actions are undone and redone in the correct order.
        """
        call_sequence = []

        class Action(UndoableAction):
            def __init__(self, n):
                self.n = n

            def do(self):
                call_sequence.append("do%s" % self.n)
                self._done()

            def undo(self):
                call_sequence.append("undo%s" % self.n)
                self._undone()

        action1 = Action(1)
        action2 = Action(2)
        action3 = Action(3)

        self.log.begin("meh")
        self.log.push(action1)
        self.log.begin("nested")
        self.log.push(action2)
        self.log.commit()
        self.log.push(action3)
        self.log.commit()

        self.log.undo()
        self.assertEqual(call_sequence, ["undo3", "undo2", "undo1"])

        call_sequence[:] = []
        self.log.redo()
        self.assertEqual(call_sequence, ["do1", "do2", "do3"])

        call_sequence[:] = []
        self.log.undo()
        self.assertEqual(call_sequence, ["undo3", "undo2", "undo1"])
Exemple #7
0
class TestTimelineUndo(TestCase):
    def setUp(self):
        app = Pitivi()
        app._startupCb(app)
        app.project_manager.newBlankProject()

        self.timeline = app.project_manager.current_project.timeline
        self.layer = GES.Layer()
        self.timeline.add_layer(self.layer)
        self.action_log = UndoableActionLog()
        self.observer = TimelineLogObserverSpy(self.action_log)
        self.observer.startObserving(self.timeline)

    def getTimelineClips(self):
        for layer in self.timeline.layers:
            for clip in layer.get_clips():
                yield clip

    @staticmethod
    def commitCb(action_log, stack, nested, stacks):
        stacks.append(stack)

    def testAddClip(self):
        stacks = []
        self.action_log.connect("commit", TestTimelineUndo.commitCb, stacks)

        clip1 = GES.TitleClip()
        self.action_log.begin("add clip")
        self.layer.add_clip(clip1)
        self.action_log.commit()

        self.assertEqual(1, len(stacks))
        stack = stacks[0]
        self.assertEqual(3, len(stack.done_actions))
        action = stack.done_actions[0]
        self.assertTrue(isinstance(action, ClipAdded))
        self.assertTrue(clip1 in self.getTimelineClips())

        self.action_log.undo()
        self.assertFalse(clip1 in self.getTimelineClips())

        self.action_log.redo()
        self.assertTrue(clip1 in self.getTimelineClips())

    def testRemoveClip(self):
        stacks = []
        self.action_log.connect("commit", TestTimelineUndo.commitCb, stacks)

        clip1 = GES.TitleClip()
        self.layer.add_clip(clip1)
        self.action_log.begin("remove clip")
        self.layer.remove_clip(clip1)
        self.action_log.commit()

        self.assertEqual(1, len(stacks))
        stack = stacks[0]
        self.assertEqual(1, len(stack.done_actions))
        action = stack.done_actions[0]
        self.assertTrue(isinstance(action, ClipRemoved))
        self.assertFalse(clip1 in self.getTimelineClips())

        self.action_log.undo()
        self.assertTrue(clip1 in self.getTimelineClips())

        self.action_log.redo()
        self.assertFalse(clip1 in self.getTimelineClips())

    def testAddEffectToClip(self):
        stacks = []
        self.action_log.connect("commit", TestTimelineUndo.commitCb, stacks)

        clip1 = GES.TitleClip()
        self.layer.add_clip(clip1)

        effect1 = GES.Effect.new("agingtv")
        self.action_log.begin("add effect")
        clip1.add(effect1)
        self.action_log.commit()

        self.assertEqual(1, len(stacks))
        stack = stacks[0]
        self.assertEqual(1, len(stack.done_actions), stack.done_actions)
        action = stack.done_actions[0]
        self.assertTrue(isinstance(action, TrackElementAdded))

        self.assertTrue(effect1 in clip1.get_children(True))
        self.assertEqual(
            1,
            len([
                effect for effect in clip1.get_children(True)
                if isinstance(effect, GES.Effect)
            ]))

        self.action_log.undo()
        self.assertFalse(effect1 in clip1.get_children(True))

        self.action_log.redo()
        self.assertEqual(
            1,
            len([
                effect for effect in clip1.get_children(True)
                if isinstance(effect, GES.Effect)
            ]))

    def testRemoveEffectFromClip(self):
        stacks = []
        self.action_log.connect("commit", TestTimelineUndo.commitCb, stacks)

        clip1 = GES.TitleClip()
        self.layer.add_clip(clip1)

        effect1 = GES.Effect.new("agingtv")
        self.action_log.begin("add effect")
        clip1.add(effect1)
        self.action_log.commit()

        self.assertEqual(1, len(stacks))
        stack = stacks[0]
        self.assertEqual(1, len(stack.done_actions), stack.done_actions)
        action = stack.done_actions[0]
        self.assertTrue(isinstance(action, TrackElementAdded))

        self.assertTrue(effect1 in clip1.get_children(True))
        self.assertEqual(
            1,
            len([
                effect for effect in clip1.get_children(True)
                if isinstance(effect, GES.Effect)
            ]))

        self.action_log.begin("remove effect")
        clip1.remove(effect1)
        self.action_log.commit()

        self.assertEqual(
            0,
            len([
                effect for effect in clip1.get_children(True)
                if isinstance(effect, GES.Effect)
            ]))

        self.action_log.undo()
        self.assertEqual(
            1,
            len([
                effect for effect in clip1.get_children(True)
                if isinstance(effect, GES.Effect)
            ]))

        self.action_log.redo()
        self.assertEqual(
            0,
            len([
                effect for effect in clip1.get_children(True)
                if isinstance(effect, GES.Effect)
            ]))

    def testChangeEffectProperty(self):
        stacks = []
        self.action_log.connect("commit", TestTimelineUndo.commitCb, stacks)

        clip1 = GES.TitleClip()
        self.layer.add_clip(clip1)

        effect1 = GES.Effect.new("agingtv")
        self.action_log.begin("add effect")
        clip1.add(effect1)
        self.action_log.commit()

        self.assertEqual(1, len(stacks))
        stack = stacks[0]
        self.assertEqual(1, len(stack.done_actions), stack.done_actions)
        action = stack.done_actions[0]
        self.assertTrue(isinstance(action, TrackElementAdded))

        self.assertTrue(effect1 in clip1.get_children(True))
        self.assertEqual(
            1,
            len([
                effect for effect in clip1.get_children(True)
                if isinstance(effect, GES.Effect)
            ]))

        self.action_log.begin("change child property")
        effect1.set_child_property("scratch-lines", 0)
        self.action_log.commit()

        self.assertEqual(effect1.get_child_property("scratch-lines")[1], 0)
        self.action_log.undo()
        self.assertEqual(effect1.get_child_property("scratch-lines")[1], 7)
        self.action_log.redo()
        self.assertEqual(effect1.get_child_property("scratch-lines")[1], 0)
        self.action_log.undo()
        self.assertTrue(effect1 in clip1.get_children(True))
        self.action_log.undo()
        self.assertFalse(effect1 in clip1.get_children(True))

    def testClipPropertyChange(self):
        stacks = []
        self.action_log.connect("commit", TestTimelineUndo.commitCb, stacks)

        clip1 = GES.TitleClip()
        self.layer.add_clip(clip1)
        clip1.set_start(5 * Gst.SECOND)
        clip1.set_duration(20 * Gst.SECOND)
        self.layer.add_clip(clip1)
        self.action_log.begin("modify clip")
        clip1.set_start(10 * Gst.SECOND)
        self.action_log.commit()

        self.assertEqual(1, len(stacks))
        stack = stacks[0]
        self.assertEqual(1, len(stack.done_actions))
        action = stack.done_actions[0]
        self.assertTrue(isinstance(action, ClipPropertyChanged))
        self.assertEqual(10 * Gst.SECOND, clip1.get_start())

        self.action_log.undo()
        self.assertEqual(5 * Gst.SECOND, clip1.get_start())
        self.action_log.redo()
        self.assertEqual(10 * Gst.SECOND, clip1.get_start())

        clip1.set_priority(10)
        self.action_log.begin("priority change")
        clip1.set_priority(20)
        self.action_log.commit()

        self.assertEqual(20, clip1.get_priority())
        self.action_log.undo()
        self.assertEqual(10, clip1.get_priority())
        self.action_log.redo()
        self.assertEqual(20, clip1.get_priority())

    def testUngroup(self):
        uri = common.TestCase.getSampleUri("tears_of_steel.webm")
        asset = GES.UriClipAsset.request_sync(uri)
        clip1 = asset.extract()
        self.layer.add_clip(clip1)

        clip1.set_start(5 * Gst.SECOND)
        clip1.set_duration(0.5 * Gst.SECOND)
        timeline_clips = list(self.getTimelineClips())
        self.assertEqual(1, len(timeline_clips), timeline_clips)
        self.assertEqual(5 * Gst.SECOND, timeline_clips[0].get_start())
        self.assertEqual(0.5 * Gst.SECOND, timeline_clips[0].get_duration())

        self.action_log.begin("ungroup")
        ungrouped = GES.Container.ungroup(clip1, False)
        self.assertEqual(2, len(ungrouped), ungrouped)
        self.action_log.commit()
        timeline_clips = list(self.getTimelineClips())
        self.assertEqual(2, len(timeline_clips), timeline_clips)
        self.assertEqual(5 * Gst.SECOND, timeline_clips[0].get_start())
        self.assertEqual(0.5 * Gst.SECOND, timeline_clips[0].get_duration())
        self.assertEqual(5 * Gst.SECOND, timeline_clips[1].get_start())
        self.assertEqual(0.5 * Gst.SECOND, timeline_clips[1].get_duration())

        self.action_log.undo()
        timeline_clips = list(self.getTimelineClips())
        self.assertEqual(1, len(timeline_clips))
        self.assertEqual(5 * Gst.SECOND, timeline_clips[0].get_start())
        self.assertEqual(0.5 * Gst.SECOND, timeline_clips[0].get_duration())