13 from xattr import NS_USER, XATTR_CREATE, XATTR_REPLACE
15 NAMESPACE = os.environ.get("NAMESPACE", NS_USER)
19 TEST_DIR = os.environ.get("TEST_DIR", ".")
20 TEST_IGNORE_XATTRS = os.environ.get("TEST_IGNORE_XATTRS", "")
21 if TEST_IGNORE_XATTRS == "":
22 TEST_IGNORE_XATTRS = []
24 TEST_IGNORE_XATTRS = TEST_IGNORE_XATTRS.split(",")
25 # The following has to be a list comprehension, not a generator, to
26 # avoid weird consequences of lazy evaluation.
27 TEST_IGNORE_XATTRS.extend([a.encode() for a in TEST_IGNORE_XATTRS])
30 USER_ATTR = NAMESPACE.decode() + "." + USER_NN
33 LARGE_VAL = "x" * 2048
36 USER_NN = USER_NN.encode()
37 USER_VAL = USER_VAL.encode()
38 USER_ATTR = USER_ATTR.encode()
39 EMPTY_VAL = EMPTY_VAL.encode()
40 LARGE_VAL = LARGE_VAL.encode()
44 def ignore_tuples(attrs):
45 """Remove ignored attributes from the output of xattr.get_all."""
46 return [attr for attr in attrs
47 if attr[0] not in TEST_IGNORE_XATTRS]
50 """Remove ignored attributes from the output of xattr.list"""
51 return [attr for attr in attrs
52 if attr not in TEST_IGNORE_XATTRS]
54 def lists_equal(attrs, value):
55 """Helper to check list equivalence, skipping TEST_IGNORE_XATTRS."""
56 assert ignore(attrs) == value
58 def tuples_equal(attrs, value):
59 """Helper to check list equivalence, skipping TEST_IGNORE_XATTRS."""
60 assert ignore_tuples(attrs) == value
62 # Fixtures and helpers
66 """per-test temp dir based in TEST_DIR"""
67 with tempfile.TemporaryDirectory(dir=TEST_DIR) as dname:
71 fh, fname = tempfile.mkstemp(".test", "xattr-", path)
74 def get_file_name(path):
75 fh, fname = get_file(path)
79 def get_file_fd(path):
80 return get_file(path)[0]
82 def get_file_object(path):
83 fd = get_file(path)[0]
87 return tempfile.mkdtemp(".test", "xattr-", path)
89 def get_symlink(path, dangling=True):
90 """create a symlink"""
91 fh, fname = get_file(path)
95 sname = fname + ".symlink"
96 os.symlink(fname, sname)
99 def get_valid_symlink(path):
100 return get_symlink(path, dangling=False)[1]
102 def get_dangling_symlink(path):
103 return get_symlink(path, dangling=True)[1]
107 return call(path).encode()
112 return pathlib.PurePath(call(path))
115 NOT_BEFORE_36 = pytest.mark.xfail(condition="sys.version_info < (3,6)",
117 NOT_PYPY = pytest.mark.xfail(condition="platform.python_implementation() == 'PyPy'",
120 # Note: user attributes are only allowed on files and directories, so
121 # we have to skip the symlinks here. See xattr(7).
123 (get_file_name, False),
124 (as_bytes(get_file_name), False),
125 pytest.param((as_fspath(get_file_name), False),
126 marks=[NOT_BEFORE_36, NOT_PYPY]),
127 (get_file_fd, False),
128 (get_file_object, False),
130 (as_bytes(get_dir), False),
131 pytest.param((as_fspath(get_dir), False),
132 marks=[NOT_BEFORE_36, NOT_PYPY]),
133 (get_valid_symlink, False),
134 (as_bytes(get_valid_symlink), False),
135 pytest.param((as_fspath(get_valid_symlink), False),
136 marks=[NOT_BEFORE_36, NOT_PYPY]),
149 "file via symlink (bytes)",
150 "file via symlink (path)",
153 ALL_ITEMS_P = ITEMS_P + [
154 (get_valid_symlink, True),
155 (as_bytes(get_valid_symlink), True),
156 (get_dangling_symlink, True),
157 (as_bytes(get_dangling_symlink), True),
160 ALL_ITEMS_D = ITEMS_D + [
162 "valid symlink (bytes)",
164 "dangling symlink (bytes)"
167 @pytest.fixture(params=ITEMS_P, ids=ITEMS_D)
168 def subject(testdir, request):
169 return request.param[0](testdir), request.param[1]
171 @pytest.fixture(params=ALL_ITEMS_P, ids=ALL_ITEMS_D)
172 def any_subject(testdir, request):
173 return request.param[0](testdir), request.param[1]
175 @pytest.fixture(params=[True, False], ids=["with namespace", "no namespace"])
179 @pytest.fixture(params=[True, False], ids=["dangling", "valid"])
180 def use_dangling(request):
185 def test_empty_value(subject):
186 item, nofollow = subject
187 xattr.set(item, USER_ATTR, EMPTY_VAL, nofollow=nofollow)
188 assert xattr.get(item, USER_ATTR, nofollow=nofollow) == EMPTY_VAL
190 def test_large_value(subject):
191 item, nofollow = subject
192 xattr.set(item, USER_ATTR, LARGE_VAL)
193 assert xattr.get(item, USER_ATTR, nofollow=nofollow) == LARGE_VAL
196 def test_file_mixed_access_deprecated(testdir):
197 """test mixed access to file (deprecated functions)"""
198 fh, fname = get_file(testdir)
199 with os.fdopen(fh) as fo:
200 lists_equal(xattr.listxattr(fname), [])
201 xattr.setxattr(fname, USER_ATTR, USER_VAL)
202 lists_equal(xattr.listxattr(fh), [USER_ATTR])
203 assert xattr.getxattr(fo, USER_ATTR) == USER_VAL
204 tuples_equal(xattr.get_all(fo), [(USER_ATTR, USER_VAL)])
205 tuples_equal(xattr.get_all(fname),
206 [(USER_ATTR, USER_VAL)])
208 def test_file_mixed_access(testdir):
209 """test mixed access to file"""
210 fh, fname = get_file(testdir)
211 with os.fdopen(fh) as fo:
212 lists_equal(xattr.list(fname), [])
213 xattr.set(fname, USER_ATTR, USER_VAL)
214 lists_equal(xattr.list(fh), [USER_ATTR])
215 assert xattr.list(fh, namespace=NAMESPACE) == [USER_NN]
216 assert xattr.get(fo, USER_ATTR) == USER_VAL
217 assert xattr.get(fo, USER_NN, namespace=NAMESPACE) == USER_VAL
218 tuples_equal(xattr.get_all(fo),
219 [(USER_ATTR, USER_VAL)])
220 assert xattr.get_all(fo, namespace=NAMESPACE) == \
221 [(USER_NN, USER_VAL)]
222 tuples_equal(xattr.get_all(fname), [(USER_ATTR, USER_VAL)])
223 assert xattr.get_all(fname, namespace=NAMESPACE) == \
224 [(USER_NN, USER_VAL)]
226 def test_replace_on_missing(subject, use_ns):
228 lists_equal(xattr.list(item), [])
229 with pytest.raises(EnvironmentError):
231 xattr.set(item, USER_NN, USER_VAL, flags=XATTR_REPLACE,
234 xattr.set(item, USER_ATTR, USER_VAL, flags=XATTR_REPLACE)
236 def test_create_on_existing(subject, use_ns):
238 lists_equal(xattr.list(item), [])
240 xattr.set(item, USER_NN, USER_VAL,
243 xattr.set(item, USER_ATTR, USER_VAL)
244 with pytest.raises(EnvironmentError):
246 xattr.set(item, USER_NN, USER_VAL,
247 flags=XATTR_CREATE, namespace=NAMESPACE)
249 xattr.set(item, USER_ATTR, USER_VAL, flags=XATTR_CREATE)
251 def test_remove_on_missing(any_subject, use_ns):
252 item, nofollow = any_subject
253 lists_equal(xattr.list(item, nofollow=nofollow), [])
254 with pytest.raises(EnvironmentError):
256 xattr.remove(item, USER_NN, namespace=NAMESPACE,
259 xattr.remove(item, USER_ATTR, nofollow=nofollow)
261 def test_set_get_remove(subject, use_ns):
263 lists_equal(xattr.list(item), [])
265 xattr.set(item, USER_NN, USER_VAL,
268 xattr.set(item, USER_ATTR, USER_VAL)
270 assert xattr.list(item, namespace=NAMESPACE) == [USER_NN]
272 lists_equal(xattr.list(item), [USER_ATTR])
273 lists_equal(xattr.list(item, namespace=EMPTY_NS),
276 assert xattr.get(item, USER_NN, namespace=NAMESPACE) == USER_VAL
278 assert xattr.get(item, USER_ATTR) == USER_VAL
280 assert xattr.get_all(item, namespace=NAMESPACE) == \
281 [(USER_NN, USER_VAL)]
283 tuples_equal(xattr.get_all(item),
284 [(USER_ATTR, USER_VAL)])
286 xattr.remove(item, USER_NN, namespace=NAMESPACE)
288 xattr.remove(item, USER_ATTR)
289 lists_equal(xattr.list(item), [])
290 tuples_equal(xattr.get_all(item), [])
292 def test_replace_on_missing_deprecated(subject):
294 lists_equal(xattr.listxattr(item), [])
295 with pytest.raises(EnvironmentError):
296 xattr.setxattr(item, USER_ATTR, USER_VAL, XATTR_REPLACE)
298 def test_create_on_existing_deprecated(subject):
300 lists_equal(xattr.listxattr(item), [])
301 xattr.setxattr(item, USER_ATTR, USER_VAL, 0)
302 with pytest.raises(EnvironmentError):
303 xattr.setxattr(item, USER_ATTR, USER_VAL, XATTR_CREATE)
305 def test_remove_on_missing_deprecated(any_subject):
306 """check deprecated list, set, get operations against an item"""
307 item, nofollow = any_subject
308 lists_equal(xattr.listxattr(item, nofollow), [])
309 with pytest.raises(EnvironmentError):
310 xattr.removexattr(item, USER_ATTR)
312 def test_set_get_remove_deprecated(subject):
313 """check deprecated list, set, get operations against an item"""
315 lists_equal(xattr.listxattr(item), [])
316 xattr.setxattr(item, USER_ATTR, USER_VAL, 0)
317 lists_equal(xattr.listxattr(item), [USER_ATTR])
318 assert xattr.getxattr(item, USER_ATTR) == USER_VAL
319 tuples_equal(xattr.get_all(item), [(USER_ATTR, USER_VAL)])
320 xattr.removexattr(item, USER_ATTR)
321 lists_equal(xattr.listxattr(item), [])
322 tuples_equal(xattr.get_all(item), [])
324 def test_many_ops(subject):
327 xattr.set(item, USER_ATTR, USER_VAL)
330 for i in range(MANYOPS_COUNT):
331 lists_equal(xattr.list(item), VL)
332 lists_equal(xattr.list(item, namespace=EMPTY_NS), VL)
333 assert xattr.list(item, namespace=NAMESPACE) == VN
334 for i in range(MANYOPS_COUNT):
335 assert xattr.get(item, USER_ATTR) == USER_VAL
336 assert xattr.get(item, USER_NN, namespace=NAMESPACE) == USER_VAL
337 for i in range(MANYOPS_COUNT):
338 tuples_equal(xattr.get_all(item),
339 [(USER_ATTR, USER_VAL)])
340 assert xattr.get_all(item, namespace=NAMESPACE) == \
341 [(USER_NN, USER_VAL)]
343 def test_many_ops_deprecated(subject):
344 """test many ops (deprecated functions)"""
346 xattr.setxattr(item, USER_ATTR, USER_VAL)
348 for i in range(MANYOPS_COUNT):
349 lists_equal(xattr.listxattr(item), VL)
350 for i in range(MANYOPS_COUNT):
351 assert xattr.getxattr(item, USER_ATTR) == USER_VAL
352 for i in range(MANYOPS_COUNT):
353 tuples_equal(xattr.get_all(item),
354 [(USER_ATTR, USER_VAL)])
356 def test_no_attributes_deprecated(any_subject):
357 """test no attributes (deprecated functions)"""
358 item, nofollow = any_subject
359 lists_equal(xattr.listxattr(item, True), [])
360 tuples_equal(xattr.get_all(item, True), [])
361 with pytest.raises(EnvironmentError):
362 xattr.getxattr(item, USER_ATTR, True)
364 def test_no_attributes(any_subject):
365 """test no attributes"""
366 item, nofollow = any_subject
367 lists_equal(xattr.list(item, nofollow=nofollow), [])
368 assert xattr.list(item, nofollow=nofollow,
369 namespace=NAMESPACE) == []
370 tuples_equal(xattr.get_all(item, nofollow=nofollow), [])
371 assert xattr.get_all(item, nofollow=nofollow,
372 namespace=NAMESPACE) == []
373 with pytest.raises(EnvironmentError):
374 xattr.get(item, USER_NN, nofollow=nofollow,
377 def test_binary_payload_deprecated(subject):
378 """test binary values (deprecated functions)"""
381 xattr.setxattr(item, USER_ATTR, BINVAL)
382 lists_equal(xattr.listxattr(item), [USER_ATTR])
383 assert xattr.getxattr(item, USER_ATTR) == BINVAL
384 tuples_equal(xattr.get_all(item), [(USER_ATTR, BINVAL)])
385 xattr.removexattr(item, USER_ATTR)
387 def test_binary_payload(subject):
388 """test binary values"""
391 xattr.set(item, USER_ATTR, BINVAL)
392 lists_equal(xattr.list(item), [USER_ATTR])
393 assert xattr.list(item, namespace=NAMESPACE) == [USER_NN]
394 assert xattr.get(item, USER_ATTR) == BINVAL
395 assert xattr.get(item, USER_NN, namespace=NAMESPACE) == BINVAL
396 tuples_equal(xattr.get_all(item), [(USER_ATTR, BINVAL)])
397 assert xattr.get_all(item, namespace=NAMESPACE) == [(USER_NN, BINVAL)]
398 xattr.remove(item, USER_ATTR)
400 def test_symlinks_user_fail(testdir, use_dangling):
401 _, sname = get_symlink(testdir, dangling=use_dangling)
402 with pytest.raises(IOError):
403 xattr.set(sname, USER_ATTR, USER_VAL, nofollow=True)
404 with pytest.raises(IOError):
405 xattr.set(sname, USER_NN, USER_VAL, namespace=NAMESPACE,
407 with pytest.raises(IOError):
408 xattr.setxattr(sname, USER_ATTR, USER_VAL, XATTR_CREATE, True)
410 @pytest.mark.parametrize(
411 "call, args", [(xattr.get, [USER_ATTR]),
413 (xattr.remove, [USER_ATTR]),
414 (xattr.get, [USER_ATTR]),
415 (xattr.set, [USER_ATTR, USER_VAL])])
416 def test_none_namespace(testdir, call, args):
417 # Don't want to use subject, since that would prevent xfail test
418 # on path objects (due to hiding the exception here).
419 f = get_file_name(testdir)
420 with pytest.raises(TypeError):
421 call(f, *args, namespace=None)
422 fd = get_file_fd(testdir)
423 with pytest.raises(TypeError):
424 call(fd, *args, namespace=None)
426 @pytest.mark.parametrize(
428 [xattr.get, xattr.list, xattr.listxattr,
429 xattr.remove, xattr.removexattr,
430 xattr.set, xattr.setxattr,
431 xattr.get, xattr.getxattr])
432 def test_wrong_call(call):
433 with pytest.raises(TypeError):
436 @pytest.mark.parametrize(
437 "call, args", [(xattr.get, [USER_ATTR]),
438 (xattr.listxattr, []),
440 (xattr.remove, [USER_ATTR]),
441 (xattr.removexattr, [USER_ATTR]),
442 (xattr.get, [USER_ATTR]),
443 (xattr.getxattr, [USER_ATTR]),
444 (xattr.set, [USER_ATTR, USER_VAL]),
445 (xattr.setxattr, [USER_ATTR, USER_VAL])])
446 def test_wrong_argument_type(call, args):
447 with pytest.raises(TypeError):
448 call(object(), *args)