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_OLD_PYPY = pytest.mark.xfail(condition="platform.python_implementation() == 'PyPy' and sys.version_info < (3, 10)",
154 NOT_MACOSX = pytest.mark.xfail(condition="sys.platform.startswith('darwin')",
155 reason="Test not supported on MacOS",
158 # Note: user attributes are only allowed on files and directories, so
159 # we have to skip the symlinks here. See xattr(7).
161 (get_file_name, False),
162 (as_bytes(get_file_name), False),
163 pytest.param((as_fspath(get_file_name), False),
164 marks=[NOT_BEFORE_36, NOT_OLD_PYPY]),
165 (get_file_fd, False),
166 (get_file_object, False),
167 (as_iostream(get_file_name), False),
169 (as_bytes(get_dir), False),
170 pytest.param((as_fspath(get_dir), False),
171 marks=[NOT_BEFORE_36, NOT_OLD_PYPY]),
172 (get_valid_symlink, False),
173 (as_bytes(get_valid_symlink), False),
174 pytest.param((as_fspath(get_valid_symlink), False),
175 marks=[NOT_BEFORE_36, NOT_OLD_PYPY]),
189 "file via symlink (bytes)",
190 "file via symlink (path)",
193 ALL_ITEMS_P = ITEMS_P + [
194 (get_valid_symlink, True),
195 (as_bytes(get_valid_symlink), True),
196 (get_dangling_symlink, True),
197 (as_bytes(get_dangling_symlink), True),
200 ALL_ITEMS_D = ITEMS_D + [
202 "valid symlink (bytes)",
204 "dangling symlink (bytes)"
207 @pytest.fixture(params=ITEMS_P, ids=ITEMS_D)
208 def subject(testdir, request):
209 with request.param[0](testdir) as value:
210 yield value, request.param[1]
212 @pytest.fixture(params=ALL_ITEMS_P, ids=ALL_ITEMS_D)
213 def any_subject(testdir, request):
214 with request.param[0](testdir) as value:
215 yield value, request.param[1]
217 @pytest.fixture(params=[True, False], ids=["with namespace", "no namespace"])
221 @pytest.fixture(params=[True, False], ids=["dangling", "valid"])
222 def use_dangling(request):
227 def test_empty_value(subject):
228 item, nofollow = subject
229 xattr.set(item, USER_ATTR, EMPTY_VAL, nofollow=nofollow)
230 assert xattr.get(item, USER_ATTR, nofollow=nofollow) == EMPTY_VAL
232 def test_large_value(subject):
233 item, nofollow = subject
234 xattr.set(item, USER_ATTR, LARGE_VAL)
235 assert xattr.get(item, USER_ATTR, nofollow=nofollow) == LARGE_VAL
237 @pytest.mark.parametrize(
238 "gen", [ get_file_and_symlink, get_file_and_fobject ])
239 def test_mixed_access(testdir, gen):
240 """test mixed access to file"""
241 with gen(testdir) as (a, b):
243 lists_equal(xattr.list(a), [])
244 lists_equal(xattr.listxattr(b), [])
247 xattr.set(a, USER_ATTR, USER_VAL)
249 # Deprecated functions
250 lists_equal(xattr.listxattr(i), [USER_ATTR])
251 assert xattr.getxattr(i, USER_ATTR) == USER_VAL
252 tuples_equal(xattr.get_all(i), [(USER_ATTR, USER_VAL)])
254 lists_equal(xattr.list(i), [USER_ATTR])
255 assert xattr.list(i, namespace=NAMESPACE) == [USER_NN]
256 assert xattr.get(i, USER_ATTR) == USER_VAL
257 assert xattr.get(i, USER_NN, namespace=NAMESPACE) == USER_VAL
258 tuples_equal(xattr.get_all(i),
259 [(USER_ATTR, USER_VAL)])
260 assert xattr.get_all(i, namespace=NAMESPACE) == \
261 [(USER_NN, USER_VAL)]
264 xattr.set(b, USER_ATTR, LARGE_VAL, flags=xattr.XATTR_REPLACE)
265 assert xattr.get(a, USER_ATTR) == LARGE_VAL
266 assert xattr.getxattr(a, USER_ATTR) == LARGE_VAL
267 xattr.removexattr(b, USER_ATTR)
268 assert xattr.get_all(a, namespace=NAMESPACE) == []
269 assert xattr.get_all(b, namespace=NAMESPACE) == []
271 def test_replace_on_missing(subject, use_ns):
273 lists_equal(xattr.list(item), [])
274 with pytest.raises(EnvironmentError):
276 xattr.set(item, USER_NN, USER_VAL, flags=XATTR_REPLACE,
279 xattr.set(item, USER_ATTR, USER_VAL, flags=XATTR_REPLACE)
281 def test_create_on_existing(subject, use_ns):
283 lists_equal(xattr.list(item), [])
285 xattr.set(item, USER_NN, USER_VAL,
288 xattr.set(item, USER_ATTR, USER_VAL)
289 with pytest.raises(EnvironmentError):
291 xattr.set(item, USER_NN, USER_VAL,
292 flags=XATTR_CREATE, namespace=NAMESPACE)
294 xattr.set(item, USER_ATTR, USER_VAL, flags=XATTR_CREATE)
296 def test_remove_on_missing(any_subject, use_ns):
297 item, nofollow = any_subject
298 lists_equal(xattr.list(item, nofollow=nofollow), [])
299 with pytest.raises(EnvironmentError):
301 xattr.remove(item, USER_NN, namespace=NAMESPACE,
304 xattr.remove(item, USER_ATTR, nofollow=nofollow)
306 def test_set_get_remove(subject, use_ns):
308 lists_equal(xattr.list(item), [])
310 xattr.set(item, USER_NN, USER_VAL,
313 xattr.set(item, USER_ATTR, USER_VAL)
315 assert xattr.list(item, namespace=NAMESPACE) == [USER_NN]
317 lists_equal(xattr.list(item), [USER_ATTR])
318 lists_equal(xattr.list(item, namespace=EMPTY_NS),
321 assert xattr.get(item, USER_NN, namespace=NAMESPACE) == USER_VAL
323 assert xattr.get(item, USER_ATTR) == USER_VAL
325 assert xattr.get_all(item, namespace=NAMESPACE) == \
326 [(USER_NN, USER_VAL)]
328 tuples_equal(xattr.get_all(item),
329 [(USER_ATTR, USER_VAL)])
331 xattr.remove(item, USER_NN, namespace=NAMESPACE)
333 xattr.remove(item, USER_ATTR)
334 lists_equal(xattr.list(item), [])
335 tuples_equal(xattr.get_all(item), [])
337 def test_replace_on_missing_deprecated(subject):
339 lists_equal(xattr.listxattr(item), [])
340 with pytest.raises(EnvironmentError):
341 xattr.setxattr(item, USER_ATTR, USER_VAL, XATTR_REPLACE)
343 def test_create_on_existing_deprecated(subject):
345 lists_equal(xattr.listxattr(item), [])
346 xattr.setxattr(item, USER_ATTR, USER_VAL, 0)
347 with pytest.raises(EnvironmentError):
348 xattr.setxattr(item, USER_ATTR, USER_VAL, XATTR_CREATE)
350 def test_remove_on_missing_deprecated(any_subject):
351 """check deprecated list, set, get operations against an item"""
352 item, nofollow = any_subject
353 lists_equal(xattr.listxattr(item, nofollow), [])
354 with pytest.raises(EnvironmentError):
355 xattr.removexattr(item, USER_ATTR)
357 def test_set_get_remove_deprecated(subject):
358 """check deprecated list, set, get operations against an item"""
360 lists_equal(xattr.listxattr(item), [])
361 xattr.setxattr(item, USER_ATTR, USER_VAL, 0)
362 lists_equal(xattr.listxattr(item), [USER_ATTR])
363 assert xattr.getxattr(item, USER_ATTR) == USER_VAL
364 tuples_equal(xattr.get_all(item), [(USER_ATTR, USER_VAL)])
365 xattr.removexattr(item, USER_ATTR)
366 lists_equal(xattr.listxattr(item), [])
367 tuples_equal(xattr.get_all(item), [])
369 def test_many_ops(subject):
372 xattr.set(item, USER_ATTR, USER_VAL)
375 for i in range(MANYOPS_COUNT):
376 lists_equal(xattr.list(item), VL)
377 lists_equal(xattr.list(item, namespace=EMPTY_NS), VL)
378 assert xattr.list(item, namespace=NAMESPACE) == VN
379 for i in range(MANYOPS_COUNT):
380 assert xattr.get(item, USER_ATTR) == USER_VAL
381 assert xattr.get(item, USER_NN, namespace=NAMESPACE) == USER_VAL
382 for i in range(MANYOPS_COUNT):
383 tuples_equal(xattr.get_all(item),
384 [(USER_ATTR, USER_VAL)])
385 assert xattr.get_all(item, namespace=NAMESPACE) == \
386 [(USER_NN, USER_VAL)]
388 def test_many_ops_deprecated(subject):
389 """test many ops (deprecated functions)"""
391 xattr.setxattr(item, USER_ATTR, USER_VAL)
393 for i in range(MANYOPS_COUNT):
394 lists_equal(xattr.listxattr(item), VL)
395 for i in range(MANYOPS_COUNT):
396 assert xattr.getxattr(item, USER_ATTR) == USER_VAL
397 for i in range(MANYOPS_COUNT):
398 tuples_equal(xattr.get_all(item),
399 [(USER_ATTR, USER_VAL)])
401 def test_no_attributes_deprecated(any_subject):
402 """test no attributes (deprecated functions)"""
403 item, nofollow = any_subject
404 lists_equal(xattr.listxattr(item, True), [])
405 tuples_equal(xattr.get_all(item, True), [])
406 with pytest.raises(EnvironmentError):
407 xattr.getxattr(item, USER_ATTR, True)
409 def test_no_attributes(any_subject):
410 """test no attributes"""
411 item, nofollow = any_subject
412 lists_equal(xattr.list(item, nofollow=nofollow), [])
413 assert xattr.list(item, nofollow=nofollow,
414 namespace=NAMESPACE) == []
415 tuples_equal(xattr.get_all(item, nofollow=nofollow), [])
416 assert xattr.get_all(item, nofollow=nofollow,
417 namespace=NAMESPACE) == []
418 with pytest.raises(EnvironmentError):
419 xattr.get(item, USER_NN, nofollow=nofollow,
422 def test_binary_payload_deprecated(subject):
423 """test binary values (deprecated functions)"""
426 xattr.setxattr(item, USER_ATTR, BINVAL)
427 lists_equal(xattr.listxattr(item), [USER_ATTR])
428 assert xattr.getxattr(item, USER_ATTR) == BINVAL
429 tuples_equal(xattr.get_all(item), [(USER_ATTR, BINVAL)])
430 xattr.removexattr(item, USER_ATTR)
432 def test_binary_payload(subject):
433 """test binary values"""
436 xattr.set(item, USER_ATTR, BINVAL)
437 lists_equal(xattr.list(item), [USER_ATTR])
438 assert xattr.list(item, namespace=NAMESPACE) == [USER_NN]
439 assert xattr.get(item, USER_ATTR) == BINVAL
440 assert xattr.get(item, USER_NN, namespace=NAMESPACE) == BINVAL
441 tuples_equal(xattr.get_all(item), [(USER_ATTR, BINVAL)])
442 assert xattr.get_all(item, namespace=NAMESPACE) == [(USER_NN, BINVAL)]
443 xattr.remove(item, USER_ATTR)
446 def test_symlinks_user_fail(testdir, use_dangling):
447 _, sname = get_symlink(testdir, dangling=use_dangling)
448 with pytest.raises(IOError):
449 xattr.set(sname, USER_ATTR, USER_VAL, nofollow=True)
450 with pytest.raises(IOError):
451 xattr.set(sname, USER_NN, USER_VAL, namespace=NAMESPACE,
453 with pytest.raises(IOError):
454 xattr.setxattr(sname, USER_ATTR, USER_VAL, XATTR_CREATE, True)
456 @pytest.mark.parametrize(
457 "call, args", [(xattr.get, [USER_ATTR]),
459 (xattr.remove, [USER_ATTR]),
460 (xattr.get, [USER_ATTR]),
461 (xattr.set, [USER_ATTR, USER_VAL])])
462 def test_none_namespace(testdir, call, args):
463 # Don't want to use subject, since that would prevent xfail test
464 # on path objects (due to hiding the exception here).
465 f = get_file_name(testdir)
466 with pytest.raises(TypeError):
467 call(f, *args, namespace=None)
468 fd = get_file_fd(testdir)
469 with pytest.raises(TypeError):
470 call(fd, *args, namespace=None)
472 @pytest.mark.parametrize(
474 [xattr.get, xattr.list, xattr.listxattr,
475 xattr.remove, xattr.removexattr,
476 xattr.set, xattr.setxattr,
477 xattr.get, xattr.getxattr])
478 def test_wrong_call(call):
479 with pytest.raises(TypeError):
482 @pytest.mark.parametrize(
483 "call, args", [(xattr.get, [USER_ATTR]),
484 (xattr.listxattr, []),
486 (xattr.remove, [USER_ATTR]),
487 (xattr.removexattr, [USER_ATTR]),
488 (xattr.get, [USER_ATTR]),
489 (xattr.getxattr, [USER_ATTR]),
490 (xattr.set, [USER_ATTR, USER_VAL]),
491 (xattr.setxattr, [USER_ATTR, USER_VAL])])
492 def test_wrong_argument_type(call, args):
493 with pytest.raises(TypeError):
494 call(object(), *args)