]> git.k1024.org Git - pyxattr.git/blob - test/test_xattr.py
Tests: remove_on_missing applies to symlinks as well
[pyxattr.git] / test / test_xattr.py
1 #
2 #
3
4 import sys
5 import tempfile
6 import os
7 import errno
8 import pytest
9
10 import xattr
11 from xattr import NS_USER, XATTR_CREATE, XATTR_REPLACE
12
13 NAMESPACE = os.environ.get("NAMESPACE", NS_USER)
14
15 EMPTY_NS = bytes()
16
17 TEST_DIR = os.environ.get("TEST_DIR", ".")
18 TEST_IGNORE_XATTRS = os.environ.get("TEST_IGNORE_XATTRS", "")
19 if TEST_IGNORE_XATTRS == "":
20     TEST_IGNORE_XATTRS = []
21 else:
22     TEST_IGNORE_XATTRS = TEST_IGNORE_XATTRS.split(",")
23     # The following has to be a list comprehension, not a generator, to
24     # avoid weird consequences of lazy evaluation.
25     TEST_IGNORE_XATTRS.extend([a.encode() for a in TEST_IGNORE_XATTRS])
26
27 USER_NN = "test"
28 USER_ATTR = NAMESPACE.decode() + "." + USER_NN
29 USER_VAL = "abc"
30 EMPTY_VAL = ""
31 LARGE_VAL = "x" * 2048
32 MANYOPS_COUNT = 16384
33
34 USER_NN = USER_NN.encode()
35 USER_VAL = USER_VAL.encode()
36 USER_ATTR = USER_ATTR.encode()
37 EMPTY_VAL = EMPTY_VAL.encode()
38 LARGE_VAL = LARGE_VAL.encode()
39
40 # Helper functions
41
42 def ignore_tuples(attrs):
43     """Remove ignored attributes from the output of xattr.get_all."""
44     return [attr for attr in attrs
45             if attr[0] not in TEST_IGNORE_XATTRS]
46
47 def ignore(attrs):
48     """Remove ignored attributes from the output of xattr.list"""
49     return [attr for attr in attrs
50             if attr not in TEST_IGNORE_XATTRS]
51
52 def lists_equal(attrs, value):
53     """Helper to check list equivalence, skipping TEST_IGNORE_XATTRS."""
54     assert ignore(attrs) == value
55
56 def tuples_equal(attrs, value):
57     """Helper to check list equivalence, skipping TEST_IGNORE_XATTRS."""
58     assert ignore_tuples(attrs) == value
59
60 # Fixtures and helpers
61
62 @pytest.fixture
63 def testdir():
64     """per-test temp dir based in TEST_DIR"""
65     with tempfile.TemporaryDirectory(dir=TEST_DIR) as dname:
66         yield dname
67
68 def get_file(path):
69     fh, fname = tempfile.mkstemp(".test", "xattr-", path)
70     return fh, fname
71
72 def get_file_name(path):
73     fh, fname = get_file(path)
74     os.close(fh)
75     return fname
76
77 def get_file_fd(path):
78     return get_file(path)[0]
79
80 def get_file_object(path):
81     fd = get_file(path)[0]
82     return os.fdopen(fd)
83
84 def get_dir(path):
85     return tempfile.mkdtemp(".test", "xattr-", path)
86
87 def get_symlink(path, dangling=True):
88     """create a symlink"""
89     fh, fname = get_file(path)
90     os.close(fh)
91     if dangling:
92         os.unlink(fname)
93     sname = fname + ".symlink"
94     os.symlink(fname, sname)
95     return fname, sname
96
97 def get_valid_symlink(path):
98     return get_symlink(path, dangling=False)[1]
99
100 def get_dangling_symlink(path):
101     return get_symlink(path, dangling=True)[1]
102
103 # Note: user attributes are only allowed on files and directories, so
104 # we have to skip the symlinks here. See xattr(7).
105 ITEMS_P = [
106     (get_file_name, False),
107     (get_file_fd, False),
108     (get_file_object, False),
109     (get_dir, False),
110     (get_valid_symlink, False),
111 ]
112
113 ITEMS_D = [
114     "file name",
115     "file FD",
116     "file object",
117     "directory",
118     "file via symlink",
119 ]
120
121 ALL_ITEMS_P = ITEMS_P + [ (get_valid_symlink, True),
122                           (get_dangling_symlink, True)]
123 ALL_ITEMS_D = ITEMS_D + ["valid symlink", "dangling symlink"]
124
125 @pytest.fixture(params=ITEMS_P, ids=ITEMS_D)
126 def subject(testdir, request):
127     return request.param[0](testdir), request.param[1]
128
129 @pytest.fixture(params=ALL_ITEMS_P, ids=ALL_ITEMS_D)
130 def any_subject(testdir, request):
131     return request.param[0](testdir), request.param[1]
132
133 @pytest.fixture(params=[True, False], ids=["with namespace", "no namespace"])
134 def use_ns(request):
135     return request.param
136
137 @pytest.fixture(params=[True, False], ids=["dangling", "valid"])
138 def use_dangling(request):
139     return request.param
140
141 ### Test functions
142
143 def test_empty_value(subject):
144     item, nofollow = subject
145     xattr.set(item, USER_ATTR, EMPTY_VAL, nofollow=nofollow)
146     assert xattr.get(item, USER_ATTR, nofollow=nofollow) == EMPTY_VAL
147
148 def test_large_value(subject):
149     item, nofollow = subject
150     xattr.set(item, USER_ATTR, LARGE_VAL)
151     assert xattr.get(item, USER_ATTR, nofollow=nofollow) == LARGE_VAL
152
153
154 def test_file_mixed_access_deprecated(testdir):
155     """test mixed access to file (deprecated functions)"""
156     fh, fname = get_file(testdir)
157     with os.fdopen(fh) as fo:
158         lists_equal(xattr.listxattr(fname), [])
159         xattr.setxattr(fname, USER_ATTR, USER_VAL)
160         lists_equal(xattr.listxattr(fh), [USER_ATTR])
161         assert xattr.getxattr(fo, USER_ATTR) == USER_VAL
162         tuples_equal(xattr.get_all(fo), [(USER_ATTR, USER_VAL)])
163         tuples_equal(xattr.get_all(fname),
164                      [(USER_ATTR, USER_VAL)])
165
166 def test_file_mixed_access(testdir):
167     """test mixed access to file"""
168     fh, fname = get_file(testdir)
169     with os.fdopen(fh) as fo:
170         lists_equal(xattr.list(fname), [])
171         xattr.set(fname, USER_ATTR, USER_VAL)
172         lists_equal(xattr.list(fh), [USER_ATTR])
173         assert xattr.list(fh, namespace=NAMESPACE) == [USER_NN]
174         assert xattr.get(fo, USER_ATTR) == USER_VAL
175         assert xattr.get(fo, USER_NN, namespace=NAMESPACE) == USER_VAL
176         tuples_equal(xattr.get_all(fo),
177                      [(USER_ATTR, USER_VAL)])
178         assert xattr.get_all(fo, namespace=NAMESPACE) == \
179             [(USER_NN, USER_VAL)]
180         tuples_equal(xattr.get_all(fname), [(USER_ATTR, USER_VAL)])
181         assert xattr.get_all(fname, namespace=NAMESPACE) == \
182             [(USER_NN, USER_VAL)]
183
184 def test_replace_on_missing(subject, use_ns):
185     item = subject[0]
186     lists_equal(xattr.list(item), [])
187     with pytest.raises(EnvironmentError):
188         if use_ns:
189             xattr.set(item, USER_NN, USER_VAL, flags=XATTR_REPLACE,
190                       namespace=NAMESPACE)
191         else:
192             xattr.set(item, USER_ATTR, USER_VAL, flags=XATTR_REPLACE)
193
194 def test_create_on_existing(subject, use_ns):
195     item = subject[0]
196     lists_equal(xattr.list(item), [])
197     if use_ns:
198         xattr.set(item, USER_NN, USER_VAL,
199                   namespace=NAMESPACE)
200     else:
201         xattr.set(item, USER_ATTR, USER_VAL)
202     with pytest.raises(EnvironmentError):
203         if use_ns:
204             xattr.set(item, USER_NN, USER_VAL,
205                       flags=XATTR_CREATE, namespace=NAMESPACE)
206         else:
207             xattr.set(item, USER_ATTR, USER_VAL, flags=XATTR_CREATE)
208
209 def test_remove_on_missing(any_subject, use_ns):
210     item, nofollow = any_subject
211     lists_equal(xattr.list(item, nofollow=nofollow), [])
212     with pytest.raises(EnvironmentError):
213         if use_ns:
214             xattr.remove(item, USER_NN, namespace=NAMESPACE,
215                          nofollow=nofollow)
216         else:
217             xattr.remove(item, USER_ATTR, nofollow=nofollow)
218
219 def test_set_get_remove(subject, use_ns):
220     item = subject[0]
221     lists_equal(xattr.list(item), [])
222     if use_ns:
223         xattr.set(item, USER_NN, USER_VAL,
224                   namespace=NAMESPACE)
225     else:
226         xattr.set(item, USER_ATTR, USER_VAL)
227     if use_ns:
228         assert xattr.list(item, namespace=NAMESPACE) == [USER_NN]
229     else:
230         lists_equal(xattr.list(item), [USER_ATTR])
231         lists_equal(xattr.list(item, namespace=EMPTY_NS),
232                     [USER_ATTR])
233     if use_ns:
234         assert xattr.get(item, USER_NN, namespace=NAMESPACE) == USER_VAL
235     else:
236         assert xattr.get(item, USER_ATTR) == USER_VAL
237     if use_ns:
238         assert xattr.get_all(item, namespace=NAMESPACE) == \
239             [(USER_NN, USER_VAL)]
240     else:
241         tuples_equal(xattr.get_all(item),
242                      [(USER_ATTR, USER_VAL)])
243     if use_ns:
244         xattr.remove(item, USER_NN, namespace=NAMESPACE)
245     else:
246         xattr.remove(item, USER_ATTR)
247     lists_equal(xattr.list(item), [])
248     tuples_equal(xattr.get_all(item), [])
249
250 def test_replace_on_missing_deprecated(subject):
251     item = subject[0]
252     lists_equal(xattr.listxattr(item), [])
253     with pytest.raises(EnvironmentError):
254         xattr.setxattr(item, USER_ATTR, USER_VAL, XATTR_REPLACE)
255
256 def test_create_on_existing_deprecated(subject):
257     item = subject[0]
258     lists_equal(xattr.listxattr(item), [])
259     xattr.setxattr(item, USER_ATTR, USER_VAL, 0)
260     with pytest.raises(EnvironmentError):
261         xattr.setxattr(item, USER_ATTR, USER_VAL, XATTR_CREATE)
262
263 def test_remove_on_missing_deprecated(any_subject):
264     """check deprecated list, set, get operations against an item"""
265     item, nofollow = any_subject
266     lists_equal(xattr.listxattr(item, nofollow), [])
267     with pytest.raises(EnvironmentError):
268         xattr.removexattr(item, USER_ATTR)
269
270 def test_set_get_remove_deprecated(subject):
271     """check deprecated list, set, get operations against an item"""
272     item = subject[0]
273     lists_equal(xattr.listxattr(item), [])
274     xattr.setxattr(item, USER_ATTR, USER_VAL, 0)
275     lists_equal(xattr.listxattr(item), [USER_ATTR])
276     assert xattr.getxattr(item, USER_ATTR) == USER_VAL
277     tuples_equal(xattr.get_all(item), [(USER_ATTR, USER_VAL)])
278     xattr.removexattr(item, USER_ATTR)
279     lists_equal(xattr.listxattr(item), [])
280     tuples_equal(xattr.get_all(item), [])
281
282 def test_many_ops(subject):
283     """test many ops"""
284     item = subject[0]
285     xattr.set(item, USER_ATTR, USER_VAL)
286     VL = [USER_ATTR]
287     VN = [USER_NN]
288     for i in range(MANYOPS_COUNT):
289         lists_equal(xattr.list(item), VL)
290         lists_equal(xattr.list(item, namespace=EMPTY_NS), VL)
291         assert xattr.list(item, namespace=NAMESPACE) == VN
292     for i in range(MANYOPS_COUNT):
293         assert xattr.get(item, USER_ATTR) == USER_VAL
294         assert xattr.get(item, USER_NN, namespace=NAMESPACE) == USER_VAL
295     for i in range(MANYOPS_COUNT):
296         tuples_equal(xattr.get_all(item),
297                      [(USER_ATTR, USER_VAL)])
298         assert xattr.get_all(item, namespace=NAMESPACE) == \
299             [(USER_NN, USER_VAL)]
300
301 def test_many_ops_deprecated(subject):
302     """test many ops (deprecated functions)"""
303     item = subject[0]
304     xattr.setxattr(item, USER_ATTR, USER_VAL)
305     VL = [USER_ATTR]
306     for i in range(MANYOPS_COUNT):
307         lists_equal(xattr.listxattr(item), VL)
308     for i in range(MANYOPS_COUNT):
309         assert xattr.getxattr(item, USER_ATTR) == USER_VAL
310     for i in range(MANYOPS_COUNT):
311         tuples_equal(xattr.get_all(item),
312                      [(USER_ATTR, USER_VAL)])
313
314 def test_no_attributes_deprecated(any_subject):
315     """test no attributes (deprecated functions)"""
316     item, nofollow = any_subject
317     lists_equal(xattr.listxattr(item, True), [])
318     tuples_equal(xattr.get_all(item, True), [])
319     with pytest.raises(EnvironmentError):
320         xattr.getxattr(item, USER_ATTR, True)
321
322 def test_no_attributes(any_subject):
323     """test no attributes"""
324     item, nofollow = any_subject
325     lists_equal(xattr.list(item, nofollow=nofollow), [])
326     assert xattr.list(item, nofollow=nofollow,
327                       namespace=NAMESPACE) == []
328     tuples_equal(xattr.get_all(item, nofollow=nofollow), [])
329     assert xattr.get_all(item, nofollow=nofollow,
330                          namespace=NAMESPACE) == []
331     with pytest.raises(EnvironmentError):
332         xattr.get(item, USER_NN, nofollow=nofollow,
333                   namespace=NAMESPACE)
334
335 def test_binary_payload_deprecated(subject):
336     """test binary values (deprecated functions)"""
337     item = subject[0]
338     BINVAL = b"abc\0def"
339     xattr.setxattr(item, USER_ATTR, BINVAL)
340     lists_equal(xattr.listxattr(item), [USER_ATTR])
341     assert xattr.getxattr(item, USER_ATTR) == BINVAL
342     tuples_equal(xattr.get_all(item), [(USER_ATTR, BINVAL)])
343     xattr.removexattr(item, USER_ATTR)
344
345 def test_binary_payload(subject):
346     """test binary values"""
347     item = subject[0]
348     BINVAL = b"abc\0def"
349     xattr.set(item, USER_ATTR, BINVAL)
350     lists_equal(xattr.list(item), [USER_ATTR])
351     assert xattr.list(item, namespace=NAMESPACE) == [USER_NN]
352     assert xattr.get(item, USER_ATTR) == BINVAL
353     assert xattr.get(item, USER_NN, namespace=NAMESPACE) == BINVAL
354     tuples_equal(xattr.get_all(item), [(USER_ATTR, BINVAL)])
355     assert xattr.get_all(item, namespace=NAMESPACE) == [(USER_NN, BINVAL)]
356     xattr.remove(item, USER_ATTR)
357
358 def test_symlinks_user_fail(testdir, use_dangling):
359     _, sname = get_symlink(testdir, dangling=use_dangling)
360     with pytest.raises(IOError):
361         xattr.set(sname, USER_ATTR, USER_VAL, nofollow=True)
362     with pytest.raises(IOError):
363         xattr.set(sname, USER_NN, USER_VAL, namespace=NAMESPACE,
364                   nofollow=True)
365     with pytest.raises(IOError):
366         xattr.setxattr(sname, USER_ATTR, USER_VAL, XATTR_CREATE, True)
367
368 def test_none_namespace(subject):
369     with pytest.raises(TypeError):
370         xattr.get(subject[0], USER_ATTR, namespace=None)
371
372 @pytest.mark.parametrize(
373     "call",
374     [xattr.get, xattr.list, xattr.listxattr,
375      xattr.remove, xattr.removexattr,
376      xattr.set, xattr.setxattr,
377      xattr.get, xattr.getxattr])
378 def test_wrong_call(call):
379     with pytest.raises(TypeError):
380         call()
381
382 @pytest.mark.parametrize(
383     "call, args", [(xattr.get, [USER_ATTR]),
384                    (xattr.listxattr, []),
385                    (xattr.list, []),
386                    (xattr.remove, [USER_ATTR]),
387                    (xattr.removexattr, [USER_ATTR]),
388                    (xattr.get, [USER_ATTR]),
389                    (xattr.getxattr, [USER_ATTR]),
390                    (xattr.set, [USER_ATTR, USER_VAL]),
391                    (xattr.setxattr, [USER_ATTR, USER_VAL])])
392 def test_wrong_argument_type(call, args):
393     with pytest.raises(TypeError):
394         call(object(), *args)