15 from xattr import NS_USER, XATTR_CREATE, XATTR_REPLACE
17 NAMESPACE = os.environ.get("NAMESPACE", NS_USER)
21 TEST_DIR = os.environ.get("TEST_DIR", ".")
22 TEST_IGNORE_XATTRS = os.environ.get("TEST_IGNORE_XATTRS", "")
23 if TEST_IGNORE_XATTRS == "":
24 TEST_IGNORE_XATTRS = []
26 TEST_IGNORE_XATTRS = TEST_IGNORE_XATTRS.split(",")
27 # The following has to be a list comprehension, not a generator, to
28 # avoid weird consequences of lazy evaluation.
29 TEST_IGNORE_XATTRS.extend([a.encode() for a in TEST_IGNORE_XATTRS])
32 USER_ATTR = NAMESPACE.decode() + "." + USER_NN
35 LARGE_VAL = "x" * 2048
38 USER_NN = USER_NN.encode()
39 USER_VAL = USER_VAL.encode()
40 USER_ATTR = USER_ATTR.encode()
41 EMPTY_VAL = EMPTY_VAL.encode()
42 LARGE_VAL = LARGE_VAL.encode()
46 def ignore_tuples(attrs):
47 """Remove ignored attributes from the output of xattr.get_all."""
48 return [attr for attr in attrs
49 if attr[0] not in TEST_IGNORE_XATTRS]
52 """Remove ignored attributes from the output of xattr.list"""
53 return [attr for attr in attrs
54 if attr not in TEST_IGNORE_XATTRS]
56 def lists_equal(attrs, value):
57 """Helper to check list equivalence, skipping TEST_IGNORE_XATTRS."""
58 assert ignore(attrs) == value
60 def tuples_equal(attrs, value):
61 """Helper to check list equivalence, skipping TEST_IGNORE_XATTRS."""
62 assert ignore_tuples(attrs) == value
64 # Fixtures and helpers
68 """per-test temp dir based in TEST_DIR"""
69 with tempfile.TemporaryDirectory(dir=TEST_DIR) as dname:
73 fh, fname = tempfile.mkstemp(".test", "xattr-", path)
76 @contextlib.contextmanager
77 def get_file_name(path):
78 fh, fname = get_file(path)
82 @contextlib.contextmanager
83 def get_file_fd(path):
84 fd = get_file(path)[0]
88 @contextlib.contextmanager
89 def get_file_object(path):
90 fd = get_file(path)[0]
91 with os.fdopen(fd) as f:
94 @contextlib.contextmanager
96 yield tempfile.mkdtemp(".test", "xattr-", path)
98 def get_symlink(path, dangling=True):
99 """create a symlink"""
100 fh, fname = get_file(path)
104 sname = fname + ".symlink"
105 os.symlink(fname, sname)
108 @contextlib.contextmanager
109 def get_valid_symlink(path):
110 yield get_symlink(path, dangling=False)[1]
112 @contextlib.contextmanager
113 def get_dangling_symlink(path):
114 yield get_symlink(path, dangling=True)[1]
116 @contextlib.contextmanager
117 def get_file_and_symlink(path):
118 yield get_symlink(path, dangling=False)
120 @contextlib.contextmanager
121 def get_file_and_fobject(path):
122 fh, fname = get_file(path)
123 with os.fdopen(fh) as fo:
126 # Wrappers that build upon existing values
128 def as_wrapper(call, fn, closer=None):
129 @contextlib.contextmanager
131 with call(path) as r:
134 if closer is not None:
139 return as_wrapper(call, lambda r: r.encode())
142 return as_wrapper(call, pathlib.PurePath)
144 def as_iostream(call):
145 opener = lambda f: io.open(f, "r")
146 closer = lambda r: r.close()
147 return as_wrapper(call, opener, closer)
149 NOT_BEFORE_36 = pytest.mark.xfail(condition="sys.version_info < (3,6)",
151 NOT_PYPY = pytest.mark.xfail(condition="platform.python_implementation() == 'PyPy'",
154 # Note: user attributes are only allowed on files and directories, so
155 # we have to skip the symlinks here. See xattr(7).
157 (get_file_name, False),
158 (as_bytes(get_file_name), False),
159 pytest.param((as_fspath(get_file_name), False),
160 marks=[NOT_BEFORE_36, NOT_PYPY]),
161 (get_file_fd, False),
162 (get_file_object, False),
163 (as_iostream(get_file_name), False),
165 (as_bytes(get_dir), False),
166 pytest.param((as_fspath(get_dir), False),
167 marks=[NOT_BEFORE_36, NOT_PYPY]),
168 (get_valid_symlink, False),
169 (as_bytes(get_valid_symlink), False),
170 pytest.param((as_fspath(get_valid_symlink), False),
171 marks=[NOT_BEFORE_36, NOT_PYPY]),
185 "file via symlink (bytes)",
186 "file via symlink (path)",
189 ALL_ITEMS_P = ITEMS_P + [
190 (get_valid_symlink, True),
191 (as_bytes(get_valid_symlink), True),
192 (get_dangling_symlink, True),
193 (as_bytes(get_dangling_symlink), True),
196 ALL_ITEMS_D = ITEMS_D + [
198 "valid symlink (bytes)",
200 "dangling symlink (bytes)"
203 @pytest.fixture(params=ITEMS_P, ids=ITEMS_D)
204 def subject(testdir, request):
205 with request.param[0](testdir) as value:
206 yield value, request.param[1]
208 @pytest.fixture(params=ALL_ITEMS_P, ids=ALL_ITEMS_D)
209 def any_subject(testdir, request):
210 with request.param[0](testdir) as value:
211 yield value, request.param[1]
213 @pytest.fixture(params=[True, False], ids=["with namespace", "no namespace"])
217 @pytest.fixture(params=[True, False], ids=["dangling", "valid"])
218 def use_dangling(request):
223 def test_empty_value(subject):
224 item, nofollow = subject
225 xattr.set(item, USER_ATTR, EMPTY_VAL, nofollow=nofollow)
226 assert xattr.get(item, USER_ATTR, nofollow=nofollow) == EMPTY_VAL
228 def test_large_value(subject):
229 item, nofollow = subject
230 xattr.set(item, USER_ATTR, LARGE_VAL)
231 assert xattr.get(item, USER_ATTR, nofollow=nofollow) == LARGE_VAL
233 @pytest.mark.parametrize(
234 "gen", [ get_file_and_symlink, get_file_and_fobject ])
235 def test_mixed_access(testdir, gen):
236 """test mixed access to file"""
237 with gen(testdir) as (a, b):
239 lists_equal(xattr.list(a), [])
240 lists_equal(xattr.listxattr(b), [])
243 xattr.set(a, USER_ATTR, USER_VAL)
245 # Deprecated functions
246 lists_equal(xattr.listxattr(i), [USER_ATTR])
247 assert xattr.getxattr(i, USER_ATTR) == USER_VAL
248 tuples_equal(xattr.get_all(i), [(USER_ATTR, USER_VAL)])
250 lists_equal(xattr.list(i), [USER_ATTR])
251 assert xattr.list(i, namespace=NAMESPACE) == [USER_NN]
252 assert xattr.get(i, USER_ATTR) == USER_VAL
253 assert xattr.get(i, USER_NN, namespace=NAMESPACE) == USER_VAL
254 tuples_equal(xattr.get_all(i),
255 [(USER_ATTR, USER_VAL)])
256 assert xattr.get_all(i, namespace=NAMESPACE) == \
257 [(USER_NN, USER_VAL)]
260 xattr.set(b, USER_ATTR, LARGE_VAL, flags=xattr.XATTR_REPLACE)
261 assert xattr.get(a, USER_ATTR) == LARGE_VAL
262 assert xattr.getxattr(a, USER_ATTR) == LARGE_VAL
263 xattr.removexattr(b, USER_ATTR)
264 assert xattr.get_all(a, namespace=NAMESPACE) == []
265 assert xattr.get_all(b, namespace=NAMESPACE) == []
267 def test_replace_on_missing(subject, use_ns):
269 lists_equal(xattr.list(item), [])
270 with pytest.raises(EnvironmentError):
272 xattr.set(item, USER_NN, USER_VAL, flags=XATTR_REPLACE,
275 xattr.set(item, USER_ATTR, USER_VAL, flags=XATTR_REPLACE)
277 def test_create_on_existing(subject, use_ns):
279 lists_equal(xattr.list(item), [])
281 xattr.set(item, USER_NN, USER_VAL,
284 xattr.set(item, USER_ATTR, USER_VAL)
285 with pytest.raises(EnvironmentError):
287 xattr.set(item, USER_NN, USER_VAL,
288 flags=XATTR_CREATE, namespace=NAMESPACE)
290 xattr.set(item, USER_ATTR, USER_VAL, flags=XATTR_CREATE)
292 def test_remove_on_missing(any_subject, use_ns):
293 item, nofollow = any_subject
294 lists_equal(xattr.list(item, nofollow=nofollow), [])
295 with pytest.raises(EnvironmentError):
297 xattr.remove(item, USER_NN, namespace=NAMESPACE,
300 xattr.remove(item, USER_ATTR, nofollow=nofollow)
302 def test_set_get_remove(subject, use_ns):
304 lists_equal(xattr.list(item), [])
306 xattr.set(item, USER_NN, USER_VAL,
309 xattr.set(item, USER_ATTR, USER_VAL)
311 assert xattr.list(item, namespace=NAMESPACE) == [USER_NN]
313 lists_equal(xattr.list(item), [USER_ATTR])
314 lists_equal(xattr.list(item, namespace=EMPTY_NS),
317 assert xattr.get(item, USER_NN, namespace=NAMESPACE) == USER_VAL
319 assert xattr.get(item, USER_ATTR) == USER_VAL
321 assert xattr.get_all(item, namespace=NAMESPACE) == \
322 [(USER_NN, USER_VAL)]
324 tuples_equal(xattr.get_all(item),
325 [(USER_ATTR, USER_VAL)])
327 xattr.remove(item, USER_NN, namespace=NAMESPACE)
329 xattr.remove(item, USER_ATTR)
330 lists_equal(xattr.list(item), [])
331 tuples_equal(xattr.get_all(item), [])
333 def test_replace_on_missing_deprecated(subject):
335 lists_equal(xattr.listxattr(item), [])
336 with pytest.raises(EnvironmentError):
337 xattr.setxattr(item, USER_ATTR, USER_VAL, XATTR_REPLACE)
339 def test_create_on_existing_deprecated(subject):
341 lists_equal(xattr.listxattr(item), [])
342 xattr.setxattr(item, USER_ATTR, USER_VAL, 0)
343 with pytest.raises(EnvironmentError):
344 xattr.setxattr(item, USER_ATTR, USER_VAL, XATTR_CREATE)
346 def test_remove_on_missing_deprecated(any_subject):
347 """check deprecated list, set, get operations against an item"""
348 item, nofollow = any_subject
349 lists_equal(xattr.listxattr(item, nofollow), [])
350 with pytest.raises(EnvironmentError):
351 xattr.removexattr(item, USER_ATTR)
353 def test_set_get_remove_deprecated(subject):
354 """check deprecated list, set, get operations against an item"""
356 lists_equal(xattr.listxattr(item), [])
357 xattr.setxattr(item, USER_ATTR, USER_VAL, 0)
358 lists_equal(xattr.listxattr(item), [USER_ATTR])
359 assert xattr.getxattr(item, USER_ATTR) == USER_VAL
360 tuples_equal(xattr.get_all(item), [(USER_ATTR, USER_VAL)])
361 xattr.removexattr(item, USER_ATTR)
362 lists_equal(xattr.listxattr(item), [])
363 tuples_equal(xattr.get_all(item), [])
365 def test_many_ops(subject):
368 xattr.set(item, USER_ATTR, USER_VAL)
371 for i in range(MANYOPS_COUNT):
372 lists_equal(xattr.list(item), VL)
373 lists_equal(xattr.list(item, namespace=EMPTY_NS), VL)
374 assert xattr.list(item, namespace=NAMESPACE) == VN
375 for i in range(MANYOPS_COUNT):
376 assert xattr.get(item, USER_ATTR) == USER_VAL
377 assert xattr.get(item, USER_NN, namespace=NAMESPACE) == USER_VAL
378 for i in range(MANYOPS_COUNT):
379 tuples_equal(xattr.get_all(item),
380 [(USER_ATTR, USER_VAL)])
381 assert xattr.get_all(item, namespace=NAMESPACE) == \
382 [(USER_NN, USER_VAL)]
384 def test_many_ops_deprecated(subject):
385 """test many ops (deprecated functions)"""
387 xattr.setxattr(item, USER_ATTR, USER_VAL)
389 for i in range(MANYOPS_COUNT):
390 lists_equal(xattr.listxattr(item), VL)
391 for i in range(MANYOPS_COUNT):
392 assert xattr.getxattr(item, USER_ATTR) == USER_VAL
393 for i in range(MANYOPS_COUNT):
394 tuples_equal(xattr.get_all(item),
395 [(USER_ATTR, USER_VAL)])
397 def test_no_attributes_deprecated(any_subject):
398 """test no attributes (deprecated functions)"""
399 item, nofollow = any_subject
400 lists_equal(xattr.listxattr(item, True), [])
401 tuples_equal(xattr.get_all(item, True), [])
402 with pytest.raises(EnvironmentError):
403 xattr.getxattr(item, USER_ATTR, True)
405 def test_no_attributes(any_subject):
406 """test no attributes"""
407 item, nofollow = any_subject
408 lists_equal(xattr.list(item, nofollow=nofollow), [])
409 assert xattr.list(item, nofollow=nofollow,
410 namespace=NAMESPACE) == []
411 tuples_equal(xattr.get_all(item, nofollow=nofollow), [])
412 assert xattr.get_all(item, nofollow=nofollow,
413 namespace=NAMESPACE) == []
414 with pytest.raises(EnvironmentError):
415 xattr.get(item, USER_NN, nofollow=nofollow,
418 def test_binary_payload_deprecated(subject):
419 """test binary values (deprecated functions)"""
422 xattr.setxattr(item, USER_ATTR, BINVAL)
423 lists_equal(xattr.listxattr(item), [USER_ATTR])
424 assert xattr.getxattr(item, USER_ATTR) == BINVAL
425 tuples_equal(xattr.get_all(item), [(USER_ATTR, BINVAL)])
426 xattr.removexattr(item, USER_ATTR)
428 def test_binary_payload(subject):
429 """test binary values"""
432 xattr.set(item, USER_ATTR, BINVAL)
433 lists_equal(xattr.list(item), [USER_ATTR])
434 assert xattr.list(item, namespace=NAMESPACE) == [USER_NN]
435 assert xattr.get(item, USER_ATTR) == BINVAL
436 assert xattr.get(item, USER_NN, namespace=NAMESPACE) == BINVAL
437 tuples_equal(xattr.get_all(item), [(USER_ATTR, BINVAL)])
438 assert xattr.get_all(item, namespace=NAMESPACE) == [(USER_NN, BINVAL)]
439 xattr.remove(item, USER_ATTR)
441 def test_symlinks_user_fail(testdir, use_dangling):
442 _, sname = get_symlink(testdir, dangling=use_dangling)
443 with pytest.raises(IOError):
444 xattr.set(sname, USER_ATTR, USER_VAL, nofollow=True)
445 with pytest.raises(IOError):
446 xattr.set(sname, USER_NN, USER_VAL, namespace=NAMESPACE,
448 with pytest.raises(IOError):
449 xattr.setxattr(sname, USER_ATTR, USER_VAL, XATTR_CREATE, True)
451 @pytest.mark.parametrize(
452 "call, args", [(xattr.get, [USER_ATTR]),
454 (xattr.remove, [USER_ATTR]),
455 (xattr.get, [USER_ATTR]),
456 (xattr.set, [USER_ATTR, USER_VAL])])
457 def test_none_namespace(testdir, call, args):
458 # Don't want to use subject, since that would prevent xfail test
459 # on path objects (due to hiding the exception here).
460 f = get_file_name(testdir)
461 with pytest.raises(TypeError):
462 call(f, *args, namespace=None)
463 fd = get_file_fd(testdir)
464 with pytest.raises(TypeError):
465 call(fd, *args, namespace=None)
467 @pytest.mark.parametrize(
469 [xattr.get, xattr.list, xattr.listxattr,
470 xattr.remove, xattr.removexattr,
471 xattr.set, xattr.setxattr,
472 xattr.get, xattr.getxattr])
473 def test_wrong_call(call):
474 with pytest.raises(TypeError):
477 @pytest.mark.parametrize(
478 "call, args", [(xattr.get, [USER_ATTR]),
479 (xattr.listxattr, []),
481 (xattr.remove, [USER_ATTR]),
482 (xattr.removexattr, [USER_ATTR]),
483 (xattr.get, [USER_ATTR]),
484 (xattr.getxattr, [USER_ATTR]),
485 (xattr.set, [USER_ATTR, USER_VAL]),
486 (xattr.setxattr, [USER_ATTR, USER_VAL])])
487 def test_wrong_argument_type(call, args):
488 with pytest.raises(TypeError):
489 call(object(), *args)