]> arthur.barton.de Git - bup.git/blob - lib/bup/t/tresolve.py
repo: add VFS resolve(); test resolve() via local and remote repos
[bup.git] / lib / bup / t / tresolve.py
1
2 from __future__ import absolute_import, print_function
3 from errno import ELOOP, ENOTDIR
4 from os import environ, symlink
5 from stat import S_IFDIR
6 from sys import stderr
7 from time import localtime, strftime
8
9 from wvtest import *
10
11 from bup import git, vfs
12 from bup.metadata import Metadata
13 from bup.repo import LocalRepo, RemoteRepo
14 from bup.test.vfs import tree_dict
15 from buptest import ex, exo, no_lingering_errors, test_tempdir
16
17 top_dir = '../../..'
18 bup_tmp = os.path.realpath('../../../t/tmp')
19 bup_path = top_dir + '/bup'
20 start_dir = os.getcwd()
21
22 ## The clear_cache() calls below are to make sure that the test starts
23 ## from a known state since at the moment the cache entry for a given
24 ## item (like a commit) can change.  For example, its meta value might
25 ## be promoted from a mode to a Metadata instance once the tree it
26 ## refers to is traversed.
27
28 def prep_and_test_repo(name, create_repo, test_repo):
29     with no_lingering_errors():
30         with test_tempdir('bup-t' + name) as tmpdir:
31             bup_dir = tmpdir + '/bup'
32             environ['GIT_DIR'] = bup_dir
33             environ['BUP_DIR'] = bup_dir
34             environ['BUP_MAIN_EXE'] = bup_path
35             ex((bup_path, 'init'))
36             git.repodir = bup_dir
37             with create_repo(bup_dir) as repo:
38                 test_repo(repo, tmpdir)
39
40 # Currently, we just test through the repos since LocalRepo resolve is
41 # just a straight redirection to vfs.resolve.
42
43 def test_resolve(repo, tmpdir):
44         data_path = tmpdir + '/src'
45         resolve = repo.resolve
46         save_time = 100000
47         save_time_str = strftime('%Y-%m-%d-%H%M%S', localtime(save_time))
48         os.mkdir(data_path)
49         os.mkdir(data_path + '/dir')
50         with open(data_path + '/file', 'w+') as tmpfile:
51             print('canary', file=tmpfile)
52         symlink('file', data_path + '/file-symlink')
53         symlink('dir', data_path + '/dir-symlink')
54         symlink('not-there', data_path + '/bad-symlink')
55         ex((bup_path, 'index', '-v', data_path))
56         ex((bup_path, 'save', '-d', str(save_time), '-tvvn', 'test',
57             '--strip', data_path))
58         ex((bup_path, 'tag', 'test-tag', 'test'))
59
60         tip_hash = exo(('git', 'show-ref', 'refs/heads/test'))[0]
61         tip_oidx = tip_hash.strip().split()[0]
62         tip_oid = tip_oidx.decode('hex')
63         tip_tree_oidx = exo(('git', 'log', '--pretty=%T', '-n1',
64                              tip_oidx))[0].strip()
65         tip_tree_oid = tip_tree_oidx.decode('hex')
66         tip_tree = tree_dict(repo, tip_tree_oid)
67         test_revlist_w_meta = vfs.RevList(meta=tip_tree['.'].meta,
68                                           oid=tip_oid)
69         expected_latest_item = vfs.Commit(meta=S_IFDIR | 0o755,
70                                           oid=tip_tree_oid,
71                                           coid=tip_oid)
72         expected_latest_item_w_meta = vfs.Commit(meta=tip_tree['.'].meta,
73                                                  oid=tip_tree_oid,
74                                                  coid=tip_oid)
75         expected_latest_link = vfs.FakeLink(meta=vfs.default_symlink_mode,
76                                             target=save_time_str)
77         expected_test_tag_item = expected_latest_item
78
79         wvstart('resolve: /')
80         vfs.clear_cache()
81         res = resolve('/')
82         wvpasseq(1, len(res))
83         wvpasseq((('', vfs._root),), res)
84         ignore, root_item = res[0]
85         root_content = frozenset(vfs.contents(repo, root_item))
86         wvpasseq(frozenset([('.', root_item),
87                             ('.tag', vfs._tags),
88                             ('test', test_revlist_w_meta)]),
89                  root_content)
90         for path in ('//', '/.', '/./', '/..', '/../',
91                      '/test/latest/dir/../../..',
92                      '/test/latest/dir/../../../',
93                      '/test/latest/dir/../../../.',
94                      '/test/latest/dir/../../..//',
95                      '/test//latest/dir/../../..',
96                      '/test/./latest/dir/../../..',
97                      '/test/././latest/dir/../../..',
98                      '/test/.//./latest/dir/../../..',
99                      '/test//.//.//latest/dir/../../..'
100                      '/test//./latest/dir/../../..'):
101             wvstart('resolve: ' + path)
102             vfs.clear_cache()
103             res = resolve(path)
104             wvpasseq((('', vfs._root),), res)
105
106         wvstart('resolve: /.tag')
107         vfs.clear_cache()
108         res = resolve('/.tag')
109         wvpasseq(2, len(res))
110         wvpasseq((('', vfs._root), ('.tag', vfs._tags)),
111                  res)
112         ignore, tag_item = res[1]
113         tag_content = frozenset(vfs.contents(repo, tag_item))
114         wvpasseq(frozenset([('.', tag_item),
115                             ('test-tag', expected_test_tag_item)]),
116                  tag_content)
117
118         wvstart('resolve: /test')
119         vfs.clear_cache()
120         res = resolve('/test')
121         wvpasseq(2, len(res))
122         wvpasseq((('', vfs._root), ('test', test_revlist_w_meta)), res)
123         ignore, test_item = res[1]
124         test_content = frozenset(vfs.contents(repo, test_item))
125         # latest has metadata here due to caching
126         wvpasseq(frozenset([('.', test_revlist_w_meta),
127                             (save_time_str, expected_latest_item_w_meta),
128                             ('latest', expected_latest_link)]),
129                  test_content)
130
131         wvstart('resolve: /test/latest')
132         vfs.clear_cache()
133         res = resolve('/test/latest')
134         wvpasseq(3, len(res))
135         expected_latest_item_w_meta = vfs.Commit(meta=tip_tree['.'].meta,
136                                                  oid=tip_tree_oid,
137                                                  coid=tip_oid)
138         expected = (('', vfs._root),
139                     ('test', test_revlist_w_meta),
140                     (save_time_str, expected_latest_item_w_meta))
141         wvpasseq(expected, res)
142         ignore, latest_item = res[2]
143         latest_content = frozenset(vfs.contents(repo, latest_item))
144         expected = frozenset((x.name, vfs.Item(oid=x.oid, meta=x.meta))
145                              for x in (tip_tree[name]
146                                        for name in ('.',
147                                                     'bad-symlink',
148                                                     'dir',
149                                                     'dir-symlink',
150                                                     'file',
151                                                     'file-symlink')))
152         wvpasseq(expected, latest_content)
153
154         wvstart('resolve: /test/latest/file')
155         vfs.clear_cache()
156         res = resolve('/test/latest/file')
157         wvpasseq(4, len(res))
158         expected_file_item_w_meta = vfs.Item(meta=tip_tree['file'].meta,
159                                              oid=tip_tree['file'].oid)
160         expected = (('', vfs._root),
161                     ('test', test_revlist_w_meta),
162                     (save_time_str, expected_latest_item_w_meta),
163                     ('file', expected_file_item_w_meta))
164         wvpasseq(expected, res)
165
166         wvstart('resolve: /test/latest/bad-symlink')
167         vfs.clear_cache()
168         res = resolve('/test/latest/bad-symlink')
169         wvpasseq(4, len(res))
170         expected = (('', vfs._root),
171                     ('test', test_revlist_w_meta),
172                     (save_time_str, expected_latest_item_w_meta),
173                     ('not-there', None))
174         wvpasseq(expected, res)
175
176         wvstart('resolve nofollow: /test/latest/bad-symlink')
177         vfs.clear_cache()
178         res = resolve('/test/latest/bad-symlink', follow=False)
179         wvpasseq(4, len(res))
180         bad_symlink_value = tip_tree['bad-symlink']
181         expected_bad_symlink_item_w_meta = vfs.Item(meta=bad_symlink_value.meta,
182                                                     oid=bad_symlink_value.oid)
183         expected = (('', vfs._root),
184                     ('test', test_revlist_w_meta),
185                     (save_time_str, expected_latest_item_w_meta),
186                     ('bad-symlink', expected_bad_symlink_item_w_meta))
187         wvpasseq(expected, res)
188
189         wvstart('resolve: /test/latest/file-symlink')
190         vfs.clear_cache()
191         res = resolve('/test/latest/file-symlink')
192         wvpasseq(4, len(res))
193         expected = (('', vfs._root),
194                     ('test', test_revlist_w_meta),
195                     (save_time_str, expected_latest_item_w_meta),
196                     ('file', expected_file_item_w_meta))
197         wvpasseq(expected, res)
198
199         wvstart('resolve nofollow: /test/latest/file-symlink')
200         vfs.clear_cache()
201         res = resolve('/test/latest/file-symlink', follow=False)
202         wvpasseq(4, len(res))
203         file_symlink_value = tip_tree['file-symlink']
204         expected_file_symlink_item_w_meta = vfs.Item(meta=file_symlink_value.meta,
205                                                      oid=file_symlink_value.oid)
206         expected = (('', vfs._root),
207                     ('test', test_revlist_w_meta),
208                     (save_time_str, expected_latest_item_w_meta),
209                     ('file-symlink', expected_file_symlink_item_w_meta))
210         wvpasseq(expected, res)
211
212         wvstart('resolve: /test/latest/missing')
213         vfs.clear_cache()
214         res = resolve('/test/latest/missing')
215         wvpasseq(4, len(res))
216         name, item = res[-1]
217         wvpasseq('missing', name)
218         wvpass(item is None)
219
220         for path in ('/test/latest/file/',
221                      '/test/latest/file/.',
222                      '/test/latest/file/..',
223                      '/test/latest/file/../',
224                      '/test/latest/file/../.',
225                      '/test/latest/file/../..',
226                      '/test/latest/file/foo'):
227             wvstart('resolve: ' + path)
228             vfs.clear_cache()
229             try:
230                 resolve(path)
231             except vfs.IOError as res_ex:
232                 wvpasseq(ENOTDIR, res_ex.errno)
233                 wvpasseq(['', 'test', save_time_str, 'file'],
234                          [name for name, item in res_ex.terminus])
235
236         for path in ('/test/latest/file-symlink/',
237                      '/test/latest/file-symlink/.',
238                      '/test/latest/file-symlink/..',
239                      '/test/latest/file-symlink/../',
240                      '/test/latest/file-symlink/../.',
241                      '/test/latest/file-symlink/../..'):
242             wvstart('resolve nofollow: ' + path)
243             vfs.clear_cache()
244             try:
245                 resolve(path, follow=False)
246             except vfs.IOError as res_ex:
247                 wvpasseq(ENOTDIR, res_ex.errno)
248                 wvpasseq(['', 'test', save_time_str, 'file'],
249                          [name for name, item in res_ex.terminus])
250
251         wvstart('resolve: non-directory parent')
252         vfs.clear_cache()
253         file_res = resolve('/test/latest/file')
254         try:
255             resolve('foo', parent=file_res)
256         except vfs.IOError as res_ex:
257             wvpasseq(ENOTDIR, res_ex.errno)
258             wvpasseq(None, res_ex.terminus)
259
260         wvstart('resolve nofollow: /test/latest/dir-symlink')
261         vfs.clear_cache()
262         res = resolve('/test/latest/dir-symlink', follow=False)
263         wvpasseq(4, len(res))
264         dir_symlink_value = tip_tree['dir-symlink']
265         expected_dir_symlink_item_w_meta = vfs.Item(meta=dir_symlink_value.meta,
266                                                      oid=dir_symlink_value.oid)
267         expected = (('', vfs._root),
268                     ('test', test_revlist_w_meta),
269                     (save_time_str, expected_latest_item_w_meta),
270                     ('dir-symlink', expected_dir_symlink_item_w_meta))
271         wvpasseq(expected, res)
272
273         dir_value = tip_tree['dir']
274         expected_dir_item = vfs.Item(oid=dir_value.oid,
275                                      meta=tree_dict(repo, dir_value.oid)['.'].meta)
276         expected = (('', vfs._root),
277                     ('test', test_revlist_w_meta),
278                     (save_time_str, expected_latest_item_w_meta),
279                     ('dir', expected_dir_item))
280         def lresolve(*args, **keys):
281             return resolve(*args, **dict(keys, follow=False))
282         for resname, resolver in (('resolve', resolve),
283                                   ('resolve nofollow', lresolve)):
284             for path in ('/test/latest/dir-symlink/',
285                          '/test/latest/dir-symlink/.'):
286                 wvstart(resname + ': ' + path)
287                 vfs.clear_cache()
288                 res = resolver(path)
289                 wvpasseq(4, len(res))
290                 wvpasseq(expected, res)
291         wvstart('resolve: /test/latest/dir-symlink')
292         vfs.clear_cache()
293         res = resolve(path)
294         wvpasseq(4, len(res))
295         wvpasseq(expected, res)
296
297 @wvtest
298 def test_local_resolve():
299     prep_and_test_repo('local-vfs-resolve',
300                        lambda x: LocalRepo(repo_dir=x), test_resolve)
301
302 @wvtest
303 def test_remote_resolve():
304     prep_and_test_repo('remote-vfs-resolve',
305                        lambda x: RemoteRepo(x), test_resolve)
306
307 def test_resolve_loop(repo, tmpdir):
308             data_path = tmpdir + '/src'
309             os.mkdir(data_path)
310             symlink('loop', data_path + '/loop')
311             ex((bup_path, 'init'))
312             ex((bup_path, 'index', '-v', data_path))
313             save_utc = 100000
314             ex((bup_path, 'save', '-d', str(save_utc), '-tvvn', 'test', '--strip',
315                 data_path))
316             save_name = strftime('%Y-%m-%d-%H%M%S', localtime(save_utc))
317             try:
318                 wvpasseq('this call should never return',
319                          repo.resolve('/test/%s/loop' % save_name))
320             except vfs.IOError as res_ex:
321                 wvpasseq(ELOOP, res_ex.errno)
322                 wvpasseq(['', 'test', save_name, 'loop'],
323                          [name for name, item in res_ex.terminus])
324
325 @wvtest
326 def test_local_resolve_loop():
327     prep_and_test_repo('local-vfs-resolve-loop',
328                        lambda x: LocalRepo(x), test_resolve_loop)
329
330 @wvtest
331 def test_remote_resolve_loop():
332     prep_and_test_repo('remote-vfs-resolve-loop',
333                        lambda x: RemoteRepo(x), test_resolve_loop)
334
335 # FIXME: add tests for the want_meta=False cases.