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