Merge lp:~mterry/duplicity/leftover-sigtar into lp:duplicity/0.6
- leftover-sigtar
- Merge into 0.6-series
Status: | Merged |
---|---|
Merged at revision: | 866 |
Proposed branch: | lp:~mterry/duplicity/leftover-sigtar |
Merge into: | lp:duplicity/0.6 |
Diff against target: |
387 lines (+123/-102) 3 files modified
bin/duplicity (+30/-19) duplicity/collections.py (+45/-15) testing/tests/cleanuptest.py (+48/-68) |
To merge this branch: | bzr merge lp:~mterry/duplicity/leftover-sigtar |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
duplicity-team | Pending | ||
Review via email: mp+125285@code.launchpad.net |
Commit message
Description of the change
So currently, duplicity does not delete signature files when doing a remove-all-but-n operation. Seems wrong, since those signature files are now useless and take up space.
This branch does several things:
1) Make remove-all-but-n operate on chains. In practice it did before, since the sets it operated on always came from complete chains (i.e. it never used only some of the sets from a chain)
2) Add a new method to get all signature chains before a certain time.
3) Use this new method to also delete signature chains during remove-all-but operations.
And it cleans up the cleanuptest.py file:
1) Removes crufty, unused code
2) Disallows changing the destination folder for the test, which no one would ever want to do and isn't really supported anyway
3) Add some additional checks to the existing test
4) Adds two new methods to test remove-all-but-n and remove-
Preview Diff
1 | === modified file 'bin/duplicity' | |||
2 | --- bin/duplicity 2012-09-13 14:08:52 +0000 | |||
3 | +++ bin/duplicity 2012-09-19 17:36:20 +0000 | |||
4 | @@ -833,6 +833,10 @@ | |||
5 | 833 | """Return string listing times of sets in setlist""" | 833 | """Return string listing times of sets in setlist""" |
6 | 834 | return "\n".join(map(lambda s: dup_time.timetopretty(s.get_time()), | 834 | return "\n".join(map(lambda s: dup_time.timetopretty(s.get_time()), |
7 | 835 | setlist)) | 835 | setlist)) |
8 | 836 | def chain_times_str(chainlist): | ||
9 | 837 | """Return string listing times of chains in chainlist""" | ||
10 | 838 | return "\n".join(map(lambda s: dup_time.timetopretty(s.end_time), | ||
11 | 839 | chainlist)) | ||
12 | 836 | 840 | ||
13 | 837 | req_list = col_stats.get_older_than_required(globals.remove_time) | 841 | req_list = col_stats.get_older_than_required(globals.remove_time) |
14 | 838 | if req_list: | 842 | if req_list: |
15 | @@ -847,32 +851,39 @@ | |||
16 | 847 | "However, it will not be deleted. To remove all your backups, " | 851 | "However, it will not be deleted. To remove all your backups, " |
17 | 848 | "manually purge the repository.")) | 852 | "manually purge the repository.")) |
18 | 849 | 853 | ||
21 | 850 | setlist = col_stats.get_older_than(globals.remove_time) | 854 | chainlist = col_stats.get_chains_older_than(globals.remove_time) |
22 | 851 | if not setlist: | 855 | if not chainlist: |
23 | 852 | log.Notice(_("No old backup sets found, nothing deleted.")) | 856 | log.Notice(_("No old backup sets found, nothing deleted.")) |
24 | 853 | return | 857 | return |
25 | 854 | if globals.force: | 858 | if globals.force: |
32 | 855 | log.Notice(gettext.ngettext("Deleting backup set at time:", | 859 | log.Notice(gettext.ngettext("Deleting backup chain at time:", |
33 | 856 | "Deleting backup sets at times:", | 860 | "Deleting backup chains at times:", |
34 | 857 | len(setlist)) + | 861 | len(chainlist)) + |
35 | 858 | "\n" + set_times_str(setlist)) | 862 | "\n" + chain_times_str(chainlist)) |
36 | 859 | setlist.reverse() # save oldest for last | 863 | # Add signature files too, since they won't be needed anymore |
37 | 860 | for set in setlist: | 864 | chainlist += col_stats.get_signature_chains_older_than(globals.remove_time) |
38 | 865 | chainlist.reverse() # save oldest for last | ||
39 | 866 | for chain in chainlist: | ||
40 | 861 | # if remove_all_inc_of_but_n_full_mode mode, remove only incrementals one and not full | 867 | # if remove_all_inc_of_but_n_full_mode mode, remove only incrementals one and not full |
43 | 862 | if globals.dry_run: | 868 | if globals.remove_all_inc_of_but_n_full_mode: |
44 | 863 | log.Notice("(Not: dry-run) Deleting set " + set.type + " " + dup_time.timetopretty(set.get_time())) | 869 | if isinstance(chain, collections.SignatureChain): |
45 | 870 | chain_desc = _("Deleting incremental signature chain %s") | ||
46 | 871 | else: | ||
47 | 872 | chain_desc = _("Deleting incremental backup chain %s") | ||
48 | 864 | else: | 873 | else: |
54 | 865 | if globals.remove_all_inc_of_but_n_full_mode and (set.type != "inc") : | 874 | if isinstance(chain, collections.SignatureChain): |
55 | 866 | log.Notice("Not deleting set " + set.type + " " + dup_time.timetopretty(set.get_time())) | 875 | chain_desc = _("Deleting complete signature chain %s") |
56 | 867 | else : | 876 | else: |
57 | 868 | log.Notice("Deleting set " + set.type + " " + dup_time.timetopretty(set.get_time())) | 877 | chain_desc = _("Deleting complete backup chain %s") |
58 | 869 | set.delete() | 878 | log.Notice(chain_desc % dup_time.timetopretty(chain.end_time)) |
59 | 879 | if not globals.dry_run: | ||
60 | 880 | chain.delete(keep_full=globals.remove_all_inc_of_but_n_full_mode) | ||
61 | 870 | col_stats.set_values(sig_chain_warning=None) | 881 | col_stats.set_values(sig_chain_warning=None) |
62 | 871 | else: | 882 | else: |
67 | 872 | log.Notice(gettext.ngettext("Found old backup set at the following time:", | 883 | log.Notice(gettext.ngettext("Found old backup chain at the following time:", |
68 | 873 | "Found old backup sets at the following times:", | 884 | "Found old backup chains at the following times:", |
69 | 874 | len(setlist)) + | 885 | len(chainlist)) + |
70 | 875 | "\n" + set_times_str(setlist) + "\n" + | 886 | "\n" + chain_times_str(chainlist) + "\n" + |
71 | 876 | _("Rerun command with --force option to actually delete.")) | 887 | _("Rerun command with --force option to actually delete.")) |
72 | 877 | 888 | ||
73 | 878 | 889 | ||
74 | 879 | 890 | ||
75 | === modified file 'duplicity/collections.py' | |||
76 | --- duplicity/collections.py 2011-11-20 16:53:09 +0000 | |||
77 | +++ duplicity/collections.py 2012-09-19 17:36:20 +0000 | |||
78 | @@ -326,13 +326,13 @@ | |||
79 | 326 | assert self.end_time | 326 | assert self.end_time |
80 | 327 | return True | 327 | return True |
81 | 328 | 328 | ||
83 | 329 | def delete(self): | 329 | def delete(self, keep_full=False): |
84 | 330 | """ | 330 | """ |
85 | 331 | Delete all sets in chain, in reverse order | 331 | Delete all sets in chain, in reverse order |
86 | 332 | """ | 332 | """ |
87 | 333 | for i in range(len(self.incset_list)-1, -1, -1): | 333 | for i in range(len(self.incset_list)-1, -1, -1): |
88 | 334 | self.incset_list[i].delete() | 334 | self.incset_list[i].delete() |
90 | 335 | if self.fullset: | 335 | if self.fullset and not keep_full: |
91 | 336 | self.fullset.delete() | 336 | self.fullset.delete() |
92 | 337 | 337 | ||
93 | 338 | def get_sets_at_time(self, time): | 338 | def get_sets_at_time(self, time): |
94 | @@ -530,7 +530,7 @@ | |||
95 | 530 | filename_to_fileobj = self.backend.get_fileobj_read | 530 | filename_to_fileobj = self.backend.get_fileobj_read |
96 | 531 | return map(filename_to_fileobj, self.get_filenames(time)) | 531 | return map(filename_to_fileobj, self.get_filenames(time)) |
97 | 532 | 532 | ||
99 | 533 | def delete(self): | 533 | def delete(self, keep_full=False): |
100 | 534 | """ | 534 | """ |
101 | 535 | Remove all files in signature set | 535 | Remove all files in signature set |
102 | 536 | """ | 536 | """ |
103 | @@ -538,12 +538,14 @@ | |||
104 | 538 | if self.archive_dir: | 538 | if self.archive_dir: |
105 | 539 | for i in range(len(self.inclist)-1, -1, -1): | 539 | for i in range(len(self.inclist)-1, -1, -1): |
106 | 540 | self.archive_dir.append(self.inclist[i]).delete() | 540 | self.archive_dir.append(self.inclist[i]).delete() |
108 | 541 | self.archive_dir.append(self.fullsig).delete() | 541 | if not keep_full: |
109 | 542 | self.archive_dir.append(self.fullsig).delete() | ||
110 | 542 | else: | 543 | else: |
111 | 543 | assert self.backend | 544 | assert self.backend |
112 | 544 | inclist_copy = self.inclist[:] | 545 | inclist_copy = self.inclist[:] |
113 | 545 | inclist_copy.reverse() | 546 | inclist_copy.reverse() |
115 | 546 | inclist_copy.append(self.fullsig) | 547 | if not keep_full: |
116 | 548 | inclist_copy.append(self.fullsig) | ||
117 | 547 | self.backend.delete(inclist_copy) | 549 | self.backend.delete(inclist_copy) |
118 | 548 | 550 | ||
119 | 549 | def get_filenames(self, time = None): | 551 | def get_filenames(self, time = None): |
120 | @@ -1009,8 +1011,6 @@ | |||
121 | 1009 | if self.matched_chain_pair: | 1011 | if self.matched_chain_pair: |
122 | 1010 | matched_sig_chain = self.matched_chain_pair[0] | 1012 | matched_sig_chain = self.matched_chain_pair[0] |
123 | 1011 | for sig_chain in self.all_sig_chains: | 1013 | for sig_chain in self.all_sig_chains: |
124 | 1012 | print sig_chain.start_time, matched_sig_chain.start_time, | ||
125 | 1013 | print sig_chain.end_time, matched_sig_chain.end_time | ||
126 | 1014 | if (sig_chain.start_time == matched_sig_chain.start_time and | 1014 | if (sig_chain.start_time == matched_sig_chain.start_time and |
127 | 1015 | sig_chain.end_time == matched_sig_chain.end_time): | 1015 | sig_chain.end_time == matched_sig_chain.end_time): |
128 | 1016 | old_sig_chains.remove(sig_chain) | 1016 | old_sig_chains.remove(sig_chain) |
129 | @@ -1032,10 +1032,43 @@ | |||
130 | 1032 | 1032 | ||
131 | 1033 | def get_chains_older_than(self, t): | 1033 | def get_chains_older_than(self, t): |
132 | 1034 | """ | 1034 | """ |
137 | 1035 | Return a list of chains older than time t | 1035 | Returns a list of backup chains older than the given time t |
138 | 1036 | """ | 1036 | |
139 | 1037 | assert self.values_set | 1037 | All of the times will be associated with an intact chain. |
140 | 1038 | return filter(lambda c: c.end_time < t, self.all_backup_chains) | 1038 | Furthermore, none of the times will be of a chain which a newer |
141 | 1039 | set may depend on. For instance, if set A is a full set older | ||
142 | 1040 | than t, and set B is an incremental based on A which is newer | ||
143 | 1041 | than t, then the time of set A will not be returned. | ||
144 | 1042 | """ | ||
145 | 1043 | assert self.values_set | ||
146 | 1044 | old_chains = [] | ||
147 | 1045 | for chain in self.all_backup_chains: | ||
148 | 1046 | if chain.end_time < t and ( | ||
149 | 1047 | not self.matched_chain_pair or | ||
150 | 1048 | chain is not self.matched_chain_pair[1]): | ||
151 | 1049 | # don't delete the active (matched) chain | ||
152 | 1050 | old_chains.append(chain) | ||
153 | 1051 | return old_chains | ||
154 | 1052 | |||
155 | 1053 | def get_signature_chains_older_than(self, t): | ||
156 | 1054 | """ | ||
157 | 1055 | Returns a list of signature chains older than the given time t | ||
158 | 1056 | |||
159 | 1057 | All of the times will be associated with an intact chain. | ||
160 | 1058 | Furthermore, none of the times will be of a chain which a newer | ||
161 | 1059 | set may depend on. For instance, if set A is a full set older | ||
162 | 1060 | than t, and set B is an incremental based on A which is newer | ||
163 | 1061 | than t, then the time of set A will not be returned. | ||
164 | 1062 | """ | ||
165 | 1063 | assert self.values_set | ||
166 | 1064 | old_chains = [] | ||
167 | 1065 | for chain in self.all_sig_chains: | ||
168 | 1066 | if chain.end_time < t and ( | ||
169 | 1067 | not self.matched_chain_pair or | ||
170 | 1068 | chain is not self.matched_chain_pair[0]): | ||
171 | 1069 | # don't delete the active (matched) chain | ||
172 | 1070 | old_chains.append(chain) | ||
173 | 1071 | return old_chains | ||
174 | 1039 | 1072 | ||
175 | 1040 | def get_last_full_backup_time(self): | 1073 | def get_last_full_backup_time(self): |
176 | 1041 | """ | 1074 | """ |
177 | @@ -1098,10 +1131,7 @@ | |||
178 | 1098 | """ | 1131 | """ |
179 | 1099 | old_sets = [] | 1132 | old_sets = [] |
180 | 1100 | for chain in self.get_chains_older_than(t): | 1133 | for chain in self.get_chains_older_than(t): |
185 | 1101 | if (not self.matched_chain_pair or | 1134 | old_sets.extend(chain.get_all_sets()) |
182 | 1102 | chain is not self.matched_chain_pair[1]): | ||
183 | 1103 | # don't delete the active (matched) chain | ||
184 | 1104 | old_sets.extend(chain.get_all_sets()) | ||
186 | 1105 | return self.sort_sets(old_sets) | 1135 | return self.sort_sets(old_sets) |
187 | 1106 | 1136 | ||
188 | 1107 | def get_older_than_required(self, t): | 1137 | def get_older_than_required(self, t): |
189 | 1108 | 1138 | ||
190 | === modified file 'testing/tests/cleanuptest.py' | |||
191 | --- testing/tests/cleanuptest.py 2011-12-05 21:49:18 +0000 | |||
192 | +++ testing/tests/cleanuptest.py 2012-09-19 17:36:20 +0000 | |||
193 | @@ -27,16 +27,10 @@ | |||
194 | 27 | 27 | ||
195 | 28 | helper.setup() | 28 | helper.setup() |
196 | 29 | 29 | ||
197 | 30 | # This can be changed to select the URL to use | ||
198 | 31 | backend_url = "file://testfiles/output" | ||
199 | 32 | |||
200 | 33 | # Extra arguments to be passed to duplicity | 30 | # Extra arguments to be passed to duplicity |
201 | 34 | other_args = ["-v0", "--no-print-statistics"] | 31 | other_args = ["-v0", "--no-print-statistics"] |
202 | 35 | #other_args = [] | 32 | #other_args = [] |
203 | 36 | 33 | ||
204 | 37 | # If this is set to true, after each backup, verify contents | ||
205 | 38 | verify = 1 | ||
206 | 39 | |||
207 | 40 | class CmdError(Exception): | 34 | class CmdError(Exception): |
208 | 41 | """Indicates an error running an external command""" | 35 | """Indicates an error running an external command""" |
209 | 42 | pass | 36 | pass |
210 | @@ -47,6 +41,7 @@ | |||
211 | 47 | """ | 41 | """ |
212 | 48 | def setUp(self): | 42 | def setUp(self): |
213 | 49 | assert not os.system("tar xzf testfiles.tar.gz > /dev/null 2>&1") | 43 | assert not os.system("tar xzf testfiles.tar.gz > /dev/null 2>&1") |
214 | 44 | self.deltmp() | ||
215 | 50 | 45 | ||
216 | 51 | def tearDown(self): | 46 | def tearDown(self): |
217 | 52 | assert not os.system("rm -rf testfiles tempdir temp2.tar") | 47 | assert not os.system("rm -rf testfiles tempdir temp2.tar") |
218 | @@ -55,6 +50,7 @@ | |||
219 | 55 | """ | 50 | """ |
220 | 56 | Run duplicity binary with given arguments and options | 51 | Run duplicity binary with given arguments and options |
221 | 57 | """ | 52 | """ |
222 | 53 | before_files = set(os.listdir("testfiles/output")) | ||
223 | 58 | options.append("--archive-dir testfiles/cache") | 54 | options.append("--archive-dir testfiles/cache") |
224 | 59 | cmd_list = ["duplicity"] | 55 | cmd_list = ["duplicity"] |
225 | 60 | cmd_list.extend(options + ["--allow-source-mismatch"]) | 56 | cmd_list.extend(options + ["--allow-source-mismatch"]) |
226 | @@ -71,6 +67,8 @@ | |||
227 | 71 | return_val = os.system(cmdline) | 67 | return_val = os.system(cmdline) |
228 | 72 | if return_val: | 68 | if return_val: |
229 | 73 | raise CmdError(return_val) | 69 | raise CmdError(return_val) |
230 | 70 | after_files = set(os.listdir("testfiles/output")) | ||
231 | 71 | return after_files - before_files | ||
232 | 74 | 72 | ||
233 | 75 | def backup(self, type, input_dir, options = [], current_time = None): | 73 | def backup(self, type, input_dir, options = [], current_time = None): |
234 | 76 | """ | 74 | """ |
235 | @@ -79,108 +77,90 @@ | |||
236 | 79 | options = options[:] | 77 | options = options[:] |
237 | 80 | if type == "full": | 78 | if type == "full": |
238 | 81 | options.insert(0, 'full') | 79 | options.insert(0, 'full') |
241 | 82 | args = [input_dir, "'%s'" % backend_url] | 80 | args = [input_dir, "file://testfiles/output"] |
242 | 83 | self.run_duplicity(args, options, current_time) | 81 | new_files = self.run_duplicity(args, options, current_time) |
243 | 84 | # If a chain ends with time X and the next full chain begins at time X, | 82 | # If a chain ends with time X and the next full chain begins at time X, |
244 | 85 | # we may trigger an assert in collections.py. This way, we avoid | 83 | # we may trigger an assert in collections.py. This way, we avoid |
245 | 86 | # such problems | 84 | # such problems |
246 | 87 | time.sleep(1) | 85 | time.sleep(1) |
258 | 88 | 86 | return new_files | |
248 | 89 | def restore(self, file_to_restore = None, time = None, options = [], | ||
249 | 90 | current_time = None): | ||
250 | 91 | options = options[:] # just nip any mutability problems in bud | ||
251 | 92 | assert not os.system("rm -rf testfiles/restore_out") | ||
252 | 93 | args = ["'%s'" % backend_url, "testfiles/restore_out"] | ||
253 | 94 | if file_to_restore: | ||
254 | 95 | options.extend(['--file-to-restore', file_to_restore]) | ||
255 | 96 | if time: | ||
256 | 97 | options.extend(['--restore-time', str(time)]) | ||
257 | 98 | self.run_duplicity(args, options, current_time) | ||
259 | 99 | 87 | ||
260 | 100 | def verify(self, dirname, file_to_verify = None, time = None, options = [], | 88 | def verify(self, dirname, file_to_verify = None, time = None, options = [], |
261 | 101 | current_time = None): | 89 | current_time = None): |
262 | 102 | options = ["verify"] + options[:] | 90 | options = ["verify"] + options[:] |
264 | 103 | args = ["'%s'" % backend_url, dirname] | 91 | args = ["file://testfiles/output", dirname] |
265 | 104 | if file_to_verify: | 92 | if file_to_verify: |
266 | 105 | options.extend(['--file-to-restore', file_to_verify]) | 93 | options.extend(['--file-to-restore', file_to_verify]) |
267 | 106 | if time: | 94 | if time: |
268 | 107 | options.extend(['--restore-time', str(time)]) | 95 | options.extend(['--restore-time', str(time)]) |
270 | 108 | self.run_duplicity(args, options, current_time) | 96 | return self.run_duplicity(args, options, current_time) |
271 | 109 | 97 | ||
272 | 110 | def cleanup(self, options = []): | 98 | def cleanup(self, options = []): |
273 | 111 | """ | 99 | """ |
274 | 112 | Run duplicity cleanup to default directory | 100 | Run duplicity cleanup to default directory |
275 | 113 | """ | 101 | """ |
276 | 114 | options = ["cleanup"] + options[:] | 102 | options = ["cleanup"] + options[:] |
279 | 115 | args = ["'%s'" % backend_url] | 103 | args = ["file://testfiles/output"] |
280 | 116 | self.run_duplicity(args, options) | 104 | return self.run_duplicity(args, options) |
281 | 117 | 105 | ||
282 | 118 | def deltmp(self): | 106 | def deltmp(self): |
283 | 119 | """ | 107 | """ |
284 | 120 | Delete temporary directories | 108 | Delete temporary directories |
285 | 121 | """ | 109 | """ |
288 | 122 | assert not os.system("rm -rf testfiles/output " | 110 | assert not os.system("rm -rf testfiles/output testfiles/cache") |
287 | 123 | "testfiles/restore_out testfiles/cache") | ||
289 | 124 | assert not os.system("mkdir testfiles/output testfiles/cache") | 111 | assert not os.system("mkdir testfiles/output testfiles/cache") |
291 | 125 | backend = duplicity.backend.get_backend(backend_url) | 112 | backend = duplicity.backend.get_backend("file://testfiles/output") |
292 | 126 | bl = backend.list() | 113 | bl = backend.list() |
293 | 127 | if bl: | 114 | if bl: |
294 | 128 | backend.delete(backend.list()) | 115 | backend.delete(backend.list()) |
295 | 129 | backend.close() | 116 | backend.close() |
296 | 130 | 117 | ||
297 | 131 | def runtest(self, dirlist, backup_options = [], restore_options = []): | ||
298 | 132 | """ | ||
299 | 133 | Run backup/restore test on directories in dirlist | ||
300 | 134 | """ | ||
301 | 135 | assert len(dirlist) >= 1 | ||
302 | 136 | self.deltmp() | ||
303 | 137 | |||
304 | 138 | # Back up directories to local backend | ||
305 | 139 | current_time = 100000 | ||
306 | 140 | self.backup("full", dirlist[0], current_time = current_time, | ||
307 | 141 | options = backup_options) | ||
308 | 142 | for new_dir in dirlist[1:]: | ||
309 | 143 | current_time += 100000 | ||
310 | 144 | self.backup("inc", new_dir, current_time = current_time, | ||
311 | 145 | options = backup_options) | ||
312 | 146 | |||
313 | 147 | # Restore each and compare them | ||
314 | 148 | for i in range(len(dirlist)): | ||
315 | 149 | dirname = dirlist[i] | ||
316 | 150 | current_time = 100000*(i + 1) | ||
317 | 151 | self.restore(time = current_time, options = restore_options) | ||
318 | 152 | self.check_same(dirname, "testfiles/restore_out") | ||
319 | 153 | if verify: | ||
320 | 154 | self.verify(dirname, | ||
321 | 155 | time = current_time, options = restore_options) | ||
322 | 156 | |||
323 | 157 | def check_same(self, filename1, filename2): | ||
324 | 158 | """ | ||
325 | 159 | Verify two filenames are the same | ||
326 | 160 | """ | ||
327 | 161 | path1, path2 = path.Path(filename1), path.Path(filename2) | ||
328 | 162 | assert path1.compare_recursive(path2, verbose = 1) | ||
329 | 163 | |||
330 | 164 | def test_cleanup_after_partial(self): | 118 | def test_cleanup_after_partial(self): |
331 | 165 | """ | 119 | """ |
332 | 166 | Regression test for https://bugs.launchpad.net/bugs/409593 | 120 | Regression test for https://bugs.launchpad.net/bugs/409593 |
333 | 167 | where duplicity deletes all the signatures during a cleanup | 121 | where duplicity deletes all the signatures during a cleanup |
334 | 168 | after a failed backup. | 122 | after a failed backup. |
335 | 169 | """ | 123 | """ |
341 | 170 | #TODO: find something better than /etc for source | 124 | good_files = self.backup("full", "/bin", options = ["--vol 1"]) |
342 | 171 | self.deltmp() | 125 | good_files |= self.backup("inc", "/bin", options = ["--vol 1"]) |
343 | 172 | self.backup("full", "/etc", options = ["--vol 1"]) | 126 | good_files |= self.backup("inc", "/bin", options = ["--vol 1"]) |
339 | 173 | self.backup("inc", "/etc", options = ["--vol 1"]) | ||
340 | 174 | self.backup("inc", "/etc", options = ["--vol 1"]) | ||
344 | 175 | # we know we're going to fail these, they are forced | 127 | # we know we're going to fail these, they are forced |
345 | 176 | try: | 128 | try: |
347 | 177 | self.backup("full", "/etc", options = ["--vol 1", "--fail 1"]) | 129 | self.backup("full", "/bin", options = ["--vol 1", "--fail 1"]) |
348 | 130 | self.fail("Not supposed to reach this far") | ||
349 | 178 | except CmdError: | 131 | except CmdError: |
351 | 179 | pass | 132 | bad_files = set(os.listdir("testfiles/output")) |
352 | 133 | bad_files -= good_files | ||
353 | 134 | self.assertNotEqual(bad_files, set()) | ||
354 | 180 | # the cleanup should go OK | 135 | # the cleanup should go OK |
355 | 181 | self.cleanup(options = ["--force"]) | 136 | self.cleanup(options = ["--force"]) |
358 | 182 | self.backup("inc", "/etc", options = ["--vol 1"]) | 137 | leftovers = set(os.listdir("testfiles/output")) |
359 | 183 | self.verify("/etc") | 138 | self.assertSetEqual(good_files, leftovers) |
360 | 139 | self.backup("inc", "/bin", options = ["--vol 1"]) | ||
361 | 140 | self.verify("/bin") | ||
362 | 141 | |||
363 | 142 | def test_remove_all_but_n(self): | ||
364 | 143 | """ | ||
365 | 144 | Test that remove-all-but-n works in the simple case. | ||
366 | 145 | """ | ||
367 | 146 | full1_files = self.backup("full", "testfiles/empty_dir") | ||
368 | 147 | full2_files = self.backup("full", "testfiles/empty_dir") | ||
369 | 148 | self.run_duplicity(["file://testfiles/output"], | ||
370 | 149 | ["remove-all-but-n", "1", "--force"]) | ||
371 | 150 | leftovers = set(os.listdir("testfiles/output")) | ||
372 | 151 | self.assertSetEqual(full2_files, leftovers) | ||
373 | 152 | |||
374 | 153 | def test_remove_all_inc_of_but_n(self): | ||
375 | 154 | """ | ||
376 | 155 | Test that remove-all-inc-of-but-n-full works in the simple case. | ||
377 | 156 | """ | ||
378 | 157 | full1_files = self.backup("full", "testfiles/empty_dir") | ||
379 | 158 | inc1_files = self.backup("inc", "testfiles/empty_dir") | ||
380 | 159 | full2_files = self.backup("full", "testfiles/empty_dir") | ||
381 | 160 | self.run_duplicity(["file://testfiles/output"], | ||
382 | 161 | ["remove-all-inc-of-but-n-full", "1", "--force"]) | ||
383 | 162 | leftovers = set(os.listdir("testfiles/output")) | ||
384 | 163 | self.assertSetEqual(full1_files | full2_files, leftovers) | ||
385 | 184 | 164 | ||
386 | 185 | 165 | ||
387 | 186 | if __name__ == "__main__": | 166 | if __name__ == "__main__": |