~ed.so/duplicity/reuse-passphrase-for-signing-fix

591 by Kenneth Loafman
* Fixed #409593 deja-dup (or duplicity) deletes all signatures
1
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
2
#
3
# Copyright 2002 Ben Escoto <ben@emerose.org>
4
# Copyright 2007 Kenneth Loafman <kenneth@loafman.com>
5
#
6
# This file is part of duplicity.
7
#
8
# Duplicity is free software; you can redistribute it and/or modify it
9
# under the terms of the GNU General Public License as published by the
10
# Free Software Foundation; either version 2 of the License, or (at your
11
# option) any later version.
12
#
13
# Duplicity is distributed in the hope that it will be useful, but
14
# WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
# General Public License for more details.
17
#
18
# You should have received a copy of the GNU General Public License
19
# along with duplicity; if not, write to the Free Software Foundation,
20
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
22
import config
23
import sys, os, unittest
24
25
import duplicity.backend
26
from duplicity import path
27
28
config.setup()
29
30
# This can be changed to select the URL to use
31
backend_url = "file://testfiles/output"
32
33
# Extra arguments to be passed to duplicity
597 by Kenneth Loafman
Merged in ~l2g/duplicity/test-compat from Larry Gilbert which made
34
other_args = ["-v0", "--no-print-statistics"]
35
#other_args = []
591 by Kenneth Loafman
* Fixed #409593 deja-dup (or duplicity) deletes all signatures
36
37
# If this is set to true, after each backup, verify contents
38
verify = 1
39
40
class CmdError(Exception):
41
    """Indicates an error running an external command"""
42
    pass
43
44
class CleanupTest(unittest.TestCase):
45
    """
46
    Test cleanup using duplicity binary
47
    """
48
    def setUp(self):
607 by Kenneth Loafman
Fix problems with unittests under Jaunty. It appears that redirection
49
        assert not os.system("tar xzf testfiles.tar.gz > /dev/null 2>&1")
591 by Kenneth Loafman
* Fixed #409593 deja-dup (or duplicity) deletes all signatures
50
51
    def tearDown(self):
52
        assert not os.system("rm -rf testfiles tempdir temp2.tar")
53
54
    def run_duplicity(self, arglist, options = [], current_time = None):
55
        """
56
        Run duplicity binary with given arguments and options
57
        """
58
        options.append("--archive-dir testfiles/cache")
59
        cmd_list = ["../duplicity-bin"]
60
        cmd_list.extend(options + ["--allow-source-mismatch"])
61
        if current_time:
62
            cmd_list.append("--current-time %s" % (current_time,))
63
        if other_args:
64
            cmd_list.extend(other_args)
65
        cmd_list.extend(arglist)
66
        cmdline = " ".join(cmd_list)
67
        #print "Running '%s'." % cmdline
68
        if not os.environ.has_key('PASSPHRASE'):
69
            os.environ['PASSPHRASE'] = 'foobar'
70
#        print "CMD: %s" % cmdline
71
        return_val = os.system(cmdline)
72
        if return_val:
73
            raise CmdError(return_val)
74
75
    def backup(self, type, input_dir, options = [], current_time = None):
76
        """
77
        Run duplicity backup to default directory
78
        """
79
        options = options[:]
80
        if type == "full":
81
            options.insert(0, 'full')
82
        args = [input_dir, "'%s'" % backend_url]
83
        self.run_duplicity(args, options, current_time)
84
85
    def restore(self, file_to_restore = None, time = None, options = [],
86
                current_time = None):
87
        options = options[:] # just nip any mutability problems in bud
88
        assert not os.system("rm -rf testfiles/restore_out")
89
        args = ["'%s'" % backend_url, "testfiles/restore_out"]
90
        if file_to_restore:
91
            options.extend(['--file-to-restore', file_to_restore])
92
        if time:
93
            options.extend(['--restore-time', str(time)])
94
        self.run_duplicity(args, options, current_time)
95
96
    def verify(self, dirname, file_to_verify = None, time = None, options = [],
97
               current_time = None):
98
        options = ["verify"] + options[:]
99
        args = ["'%s'" % backend_url, dirname]
100
        if file_to_verify:
101
            options.extend(['--file-to-restore', file_to_verify])
102
        if time:
103
            options.extend(['--restore-time', str(time)])
104
        self.run_duplicity(args, options, current_time)
105
106
    def cleanup(self, options = []):
107
        """
108
        Run duplicity cleanup to default directory
109
        """
110
        options = ["cleanup"] + options[:]
111
        args = ["'%s'" % backend_url]
112
        self.run_duplicity(args, options)
113
114
    def deltmp(self):
115
        """
116
        Delete temporary directories
117
        """
118
        assert not os.system("rm -rf testfiles/output "
119
                             "testfiles/restore_out testfiles/cache")
120
        assert not os.system("mkdir testfiles/output testfiles/cache")
121
        backend = duplicity.backend.get_backend(backend_url)
122
        bl = backend.list()
123
        if bl:
124
            backend.delete(backend.list())
125
        backend.close()
126
127
    def runtest(self, dirlist, backup_options = [], restore_options = []):
128
        """
129
        Run backup/restore test on directories in dirlist
130
        """
131
        assert len(dirlist) >= 1
132
        self.deltmp()
133
134
        # Back up directories to local backend
135
        current_time = 100000
136
        self.backup("full", dirlist[0], current_time = current_time,
137
                    options = backup_options)
138
        for new_dir in dirlist[1:]:
139
            current_time += 100000
140
            self.backup("inc", new_dir, current_time = current_time,
141
                        options = backup_options)
142
143
        # Restore each and compare them
144
        for i in range(len(dirlist)):
145
            dirname = dirlist[i]
146
            current_time = 100000*(i + 1)
147
            self.restore(time = current_time, options = restore_options)
148
            self.check_same(dirname, "testfiles/restore_out")
149
            if verify:
150
                self.verify(dirname,
151
                            time = current_time, options = restore_options)
152
153
    def check_same(self, filename1, filename2):
154
        """
155
        Verify two filenames are the same
156
        """
157
        path1, path2 = path.Path(filename1), path.Path(filename2)
158
        assert path1.compare_recursive(path2, verbose = 1)
159
160
    def test_cleanup_after_partial(self):
161
        """
162
        Regression test for https://bugs.launchpad.net/bugs/409593
163
        where duplicity deletes all the signatures during a cleanup
164
        after a failed backup.
165
        """
166
        #TODO: find something better than /etc for source
167
        self.deltmp()
168
        self.backup("full", "/etc", options = ["--vol 1"])
169
        self.backup("inc", "/etc", options = ["--vol 1"])
170
        self.backup("inc", "/etc", options = ["--vol 1"])
171
        # we know we're going to fail these, they are forced
172
        try:
173
            self.backup("full", "/etc", options = ["--vol 1", "--fail 1"])
174
        except CmdError:
175
            pass
176
        # the cleanup should go OK
177
        self.cleanup(options = ["--force"])
178
        self.backup("inc", "/etc", options = ["--vol 1"])
179
        self.verify("/etc")
180
181
182
if __name__ == "__main__":
183
    unittest.main()