]> git.k1024.org Git - pyxattr.git/blob - test/test_xattr.py
Add explicit test for symlink creation failure
[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 #    (get_valid_symlink, True),
118 #    (get_dangling_symlink, True),
119 ]
120
121 ITEMS_D = [
122     "file name",
123     "file FD",
124     "file object",
125     "directory",
126     "file via symlink",
127 #    "valid symlink",
128 #    "dangling symlink",
129 ]
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=[True, False], ids=["with namespace", "no namespace"])
136 def use_ns(request):
137     return request.param
138
139 @pytest.fixture(params=[True, False], ids=["dangling", "valid"])
140 def use_dangling(request):
141     return request.param
142
143 ### Test functions
144
145 def test_empty_value(subject):
146     item, nofollow = subject
147     xattr.set(item, USER_ATTR, EMPTY_VAL, nofollow=nofollow)
148     assert xattr.get(item, USER_ATTR, nofollow=nofollow) == EMPTY_VAL
149
150 def test_large_value(subject):
151     item, nofollow = subject
152     xattr.set(item, USER_ATTR, LARGE_VAL)
153     assert xattr.get(item, USER_ATTR, nofollow=nofollow) == LARGE_VAL
154
155
156 def test_file_mixed_access_deprecated(testdir):
157     """test mixed access to file (deprecated functions)"""
158     fh, fname = get_file(testdir)
159     with os.fdopen(fh) as fo:
160         lists_equal(xattr.listxattr(fname), [])
161         xattr.setxattr(fname, USER_ATTR, USER_VAL)
162         lists_equal(xattr.listxattr(fh), [USER_ATTR])
163         assert xattr.getxattr(fo, USER_ATTR) == USER_VAL
164         tuples_equal(xattr.get_all(fo), [(USER_ATTR, USER_VAL)])
165         tuples_equal(xattr.get_all(fname),
166                      [(USER_ATTR, USER_VAL)])
167
168 def test_file_mixed_access(testdir):
169     """test mixed access to file"""
170     fh, fname = get_file(testdir)
171     with os.fdopen(fh) as fo:
172         lists_equal(xattr.list(fname), [])
173         xattr.set(fname, USER_ATTR, USER_VAL)
174         lists_equal(xattr.list(fh), [USER_ATTR])
175         assert xattr.list(fh, namespace=NAMESPACE) == [USER_NN]
176         assert xattr.get(fo, USER_ATTR) == USER_VAL
177         assert xattr.get(fo, USER_NN, namespace=NAMESPACE) == USER_VAL
178         tuples_equal(xattr.get_all(fo),
179                      [(USER_ATTR, USER_VAL)])
180         assert xattr.get_all(fo, namespace=NAMESPACE) == \
181             [(USER_NN, USER_VAL)]
182         tuples_equal(xattr.get_all(fname), [(USER_ATTR, USER_VAL)])
183         assert xattr.get_all(fname, namespace=NAMESPACE) == \
184             [(USER_NN, USER_VAL)]
185
186 def test_ListSetGet(subject, use_ns):
187     """check list, set, get operations against an item"""
188     item = subject[0]
189     lists_equal(xattr.list(item), [])
190     with pytest.raises(EnvironmentError):
191         if use_ns:
192             xattr.set(item, USER_NN, USER_VAL, flags=XATTR_REPLACE,
193                       namespace=NAMESPACE)
194         else:
195             xattr.set(item, USER_ATTR, USER_VAL, flags=XATTR_REPLACE)
196     if use_ns:
197         xattr.set(item, USER_NN, USER_VAL,
198                   namespace=NAMESPACE)
199     else:
200         xattr.set(item, USER_ATTR, USER_VAL)
201     with pytest.raises(EnvironmentError):
202         if use_ns:
203             xattr.set(item, USER_NN, USER_VAL,
204                       flags=XATTR_CREATE, namespace=NAMESPACE)
205         else:
206             xattr.set(item, USER_ATTR, USER_VAL, flags=XATTR_CREATE)
207     if use_ns:
208         assert xattr.list(item, namespace=NAMESPACE) == [USER_NN]
209     else:
210         lists_equal(xattr.list(item), [USER_ATTR])
211         lists_equal(xattr.list(item, namespace=EMPTY_NS),
212                     [USER_ATTR])
213     if use_ns:
214         assert xattr.get(item, USER_NN, namespace=NAMESPACE) == USER_VAL
215     else:
216         assert xattr.get(item, USER_ATTR) == USER_VAL
217     if use_ns:
218         assert xattr.get_all(item, namespace=NAMESPACE) == \
219             [(USER_NN, USER_VAL)]
220     else:
221         tuples_equal(xattr.get_all(item),
222                      [(USER_ATTR, USER_VAL)])
223     if use_ns:
224         xattr.remove(item, USER_NN, namespace=NAMESPACE)
225     else:
226         xattr.remove(item, USER_ATTR)
227     lists_equal(xattr.list(item), [])
228     tuples_equal(xattr.get_all(item), [])
229     with pytest.raises(EnvironmentError):
230         if use_ns:
231             xattr.remove(item, USER_NN, namespace=NAMESPACE)
232         else:
233             xattr.remove(item, USER_ATTR)
234
235 def test_ListSetGetDeprecated(subject):
236     """check deprecated list, set, get operations against an item"""
237     item = subject[0]
238     lists_equal(xattr.listxattr(item), [])
239     with pytest.raises(EnvironmentError):
240         xattr.setxattr(item, USER_ATTR, USER_VAL, XATTR_REPLACE)
241     xattr.setxattr(item, USER_ATTR, USER_VAL, 0)
242     with pytest.raises(EnvironmentError):
243         xattr.setxattr(item, USER_ATTR, USER_VAL, XATTR_CREATE)
244     lists_equal(xattr.listxattr(item), [USER_ATTR])
245     assert xattr.getxattr(item, USER_ATTR) == USER_VAL
246     tuples_equal(xattr.get_all(item), [(USER_ATTR, USER_VAL)])
247     xattr.removexattr(item, USER_ATTR)
248     lists_equal(xattr.listxattr(item), [])
249     tuples_equal(xattr.get_all(item), [])
250     with pytest.raises(EnvironmentError):
251         xattr.removexattr(item, USER_ATTR)
252
253 def test_many_ops(subject):
254     """test many ops"""
255     item = subject[0]
256     xattr.set(item, USER_ATTR, USER_VAL)
257     VL = [USER_ATTR]
258     VN = [USER_NN]
259     for i in range(MANYOPS_COUNT):
260         lists_equal(xattr.list(item), VL)
261         lists_equal(xattr.list(item, namespace=EMPTY_NS), VL)
262         assert xattr.list(item, namespace=NAMESPACE) == VN
263     for i in range(MANYOPS_COUNT):
264         assert xattr.get(item, USER_ATTR) == USER_VAL
265         assert xattr.get(item, USER_NN, namespace=NAMESPACE) == USER_VAL
266     for i in range(MANYOPS_COUNT):
267         tuples_equal(xattr.get_all(item),
268                      [(USER_ATTR, USER_VAL)])
269         assert xattr.get_all(item, namespace=NAMESPACE) == \
270             [(USER_NN, USER_VAL)]
271
272 def test_many_ops_deprecated(subject):
273     """test many ops (deprecated functions)"""
274     item = subject[0]
275     xattr.setxattr(item, USER_ATTR, USER_VAL)
276     VL = [USER_ATTR]
277     for i in range(MANYOPS_COUNT):
278         lists_equal(xattr.listxattr(item), VL)
279     for i in range(MANYOPS_COUNT):
280         assert xattr.getxattr(item, USER_ATTR) == USER_VAL
281     for i in range(MANYOPS_COUNT):
282         tuples_equal(xattr.get_all(item),
283                      [(USER_ATTR, USER_VAL)])
284
285 def test_no_attributes_deprecated(subject):
286     """test no attributes (deprecated functions)"""
287     item = subject[0]
288     lists_equal(xattr.listxattr(item), [])
289     tuples_equal(xattr.get_all(item), [])
290     with pytest.raises(EnvironmentError):
291         xattr.getxattr(item, USER_ATTR)
292
293 def test_no_attributes_deprecated_symlinks(testdir, use_dangling):
294     """test no attributes on symlinks (deprecated functions)"""
295     _, sname = get_symlink(testdir, dangling=use_dangling)
296     lists_equal(xattr.listxattr(sname, True), [])
297     tuples_equal(xattr.get_all(sname, nofollow=True), [])
298     with pytest.raises(EnvironmentError):
299         xattr.getxattr(sname, USER_ATTR, True)
300
301 def test_no_attributes(subject):
302     """test no attributes"""
303     item = subject[0]
304     lists_equal(xattr.list(item), [])
305     assert xattr.list(item, namespace=NAMESPACE) == []
306     tuples_equal(xattr.get_all(item), [])
307     assert xattr.get_all(item, namespace=NAMESPACE) == []
308     with pytest.raises(EnvironmentError):
309         xattr.get(item, USER_NN, namespace=NAMESPACE)
310
311 def test_no_attributes_symlinks(testdir, use_dangling):
312     """test no attributes on symlinks"""
313     _, sname = get_symlink(testdir, dangling=use_dangling)
314     lists_equal(xattr.list(sname, nofollow=True), [])
315     assert xattr.list(sname, nofollow=True,
316                       namespace=NAMESPACE) == []
317     tuples_equal(xattr.get_all(sname, nofollow=True), [])
318     assert xattr.get_all(sname, nofollow=True,
319                          namespace=NAMESPACE) == []
320     with pytest.raises(EnvironmentError):
321         xattr.get(sname, USER_NN, namespace=NAMESPACE, nofollow=True)
322
323 def test_binary_payload_deprecated(subject):
324     """test binary values (deprecated functions)"""
325     item = subject[0]
326     BINVAL = b"abc\0def"
327     xattr.setxattr(item, USER_ATTR, BINVAL)
328     lists_equal(xattr.listxattr(item), [USER_ATTR])
329     assert xattr.getxattr(item, USER_ATTR) == BINVAL
330     tuples_equal(xattr.get_all(item), [(USER_ATTR, BINVAL)])
331     xattr.removexattr(item, USER_ATTR)
332
333 def test_binary_payload(subject):
334     """test binary values"""
335     item = subject[0]
336     BINVAL = b"abc\0def"
337     xattr.set(item, USER_ATTR, BINVAL)
338     lists_equal(xattr.list(item), [USER_ATTR])
339     assert xattr.list(item, namespace=NAMESPACE) == [USER_NN]
340     assert xattr.get(item, USER_ATTR) == BINVAL
341     assert xattr.get(item, USER_NN, namespace=NAMESPACE) == BINVAL
342     tuples_equal(xattr.get_all(item), [(USER_ATTR, BINVAL)])
343     assert xattr.get_all(item, namespace=NAMESPACE) == [(USER_NN, BINVAL)]
344     xattr.remove(item, USER_ATTR)
345
346 def test_symlinks_user_fail(testdir, use_dangling):
347     _, sname = get_symlink(testdir, dangling=use_dangling)
348     with pytest.raises(IOError):
349         xattr.set(sname, USER_ATTR, USER_VAL, nofollow=True)
350     with pytest.raises(IOError):
351         xattr.set(sname, USER_NN, USER_VAL, namespace=NAMESPACE,
352                   nofollow=True)
353     with pytest.raises(IOError):
354         xattr.setxattr(sname, USER_ATTR, USER_VAL, XATTR_CREATE, True)
355
356 def test_none_namespace(subject):
357     with pytest.raises(TypeError):
358         xattr.get(subject[0], USER_ATTR, namespace=None)
359
360 @pytest.mark.parametrize(
361     "call",
362     [xattr.get, xattr.list, xattr.listxattr,
363      xattr.remove, xattr.removexattr,
364      xattr.set, xattr.setxattr,
365      xattr.get, xattr.getxattr])
366 def test_wrong_call(call):
367     with pytest.raises(TypeError):
368         call()
369
370 @pytest.mark.parametrize(
371     "call, args", [(xattr.get, [USER_ATTR]),
372                    (xattr.listxattr, []),
373                    (xattr.list, []),
374                    (xattr.remove, [USER_ATTR]),
375                    (xattr.removexattr, [USER_ATTR]),
376                    (xattr.get, [USER_ATTR]),
377                    (xattr.getxattr, [USER_ATTR]),
378                    (xattr.set, [USER_ATTR, USER_VAL]),
379                    (xattr.setxattr, [USER_ATTR, USER_VAL])])
380 def test_wrong_argument_type(call, args):
381     with pytest.raises(TypeError):
382         call(object(), *args)