]> git.k1024.org Git - pylibacl.git/blob - tests/test_acls.py
tests: Py3 cleanup
[pylibacl.git] / tests / test_acls.py
1 #
2 #
3
4 """Unittests for the posix1e module"""
5
6 #  Copyright (C) 2002-2009, 2012, 2014, 2015 Iustin Pop <iustin@k1024.org>
7 #
8 #  This library is free software; you can redistribute it and/or
9 #  modify it under the terms of the GNU Lesser General Public
10 #  License as published by the Free Software Foundation; either
11 #  version 2.1 of the License, or (at your option) any later version.
12 #
13 #  This library is distributed in the hope that it will be useful,
14 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 #  Lesser General Public License for more details.
17 #
18 #  You should have received a copy of the GNU Lesser General Public
19 #  License along with this library; if not, write to the Free Software
20 #  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 #  02110-1301  USA
22
23
24 import unittest
25 import os
26 import tempfile
27 import sys
28 import platform
29 import re
30 import errno
31 import operator
32 import pytest
33 import contextlib
34
35 import posix1e
36 from posix1e import *
37
38 TEST_DIR = os.environ.get("TEST_DIR", ".")
39
40 BASIC_ACL_TEXT = "u::rw,g::r,o::-"
41
42 # Permset permission information
43 PERMSETS = {
44   posix1e.ACL_READ: ("read", posix1e.Permset.read),
45   posix1e.ACL_WRITE: ("write", posix1e.Permset.write),
46   posix1e.ACL_EXECUTE: ("execute", posix1e.Permset.execute),
47   }
48
49 ALL_TAGS = [
50   (posix1e.ACL_USER, "user"),
51   (posix1e.ACL_GROUP, "group"),
52   (posix1e.ACL_USER_OBJ, "user object"),
53   (posix1e.ACL_GROUP_OBJ, "group object"),
54   (posix1e.ACL_MASK, "mask"),
55   (posix1e.ACL_OTHER, "other"),
56 ]
57
58 ALL_TAG_VALUES = [i[0] for i in ALL_TAGS]
59 ALL_TAG_DESCS = [i[1] for i in ALL_TAGS]
60
61 # Fixtures and helpers
62
63 def ignore_ioerror(errnum, fn, *args, **kwargs):
64     """Call a function while ignoring some IOErrors.
65
66     This is needed as some OSes (e.g. FreeBSD) return failure (EINVAL)
67     when doing certain operations on an invalid ACL.
68
69     """
70     try:
71         fn(*args, **kwargs)
72     except IOError:
73         err = sys.exc_info()[1]
74         if err.errno == errnum:
75             return
76         raise
77
78 @pytest.fixture
79 def testdir():
80     """per-test temp dir based in TEST_DIR"""
81     with tempfile.TemporaryDirectory(dir=TEST_DIR) as dname:
82         yield dname
83
84 def get_file(path):
85     fh, fname = tempfile.mkstemp(".test", "xattr-", path)
86     return fh, fname
87
88 @contextlib.contextmanager
89 def get_file_name(path):
90     fh, fname = get_file(path)
91     os.close(fh)
92     yield fname
93
94 @contextlib.contextmanager
95 def get_file_fd(path):
96     fd = get_file(path)[0]
97     yield fd
98     os.close(fd)
99
100 @contextlib.contextmanager
101 def get_file_object(path):
102     fd = get_file(path)[0]
103     with os.fdopen(fd) as f:
104         yield f
105
106 @contextlib.contextmanager
107 def get_dir(path):
108     yield tempfile.mkdtemp(".test", "xattr-", path)
109
110 def get_symlink(path, dangling=True):
111     """create a symlink"""
112     fh, fname = get_file(path)
113     os.close(fh)
114     if dangling:
115         os.unlink(fname)
116     sname = fname + ".symlink"
117     os.symlink(fname, sname)
118     return fname, sname
119
120 @contextlib.contextmanager
121 def get_valid_symlink(path):
122     yield get_symlink(path, dangling=False)[1]
123
124 @contextlib.contextmanager
125 def get_dangling_symlink(path):
126     yield get_symlink(path, dangling=True)[1]
127
128 @contextlib.contextmanager
129 def get_file_and_symlink(path):
130     yield get_symlink(path, dangling=False)
131
132 @contextlib.contextmanager
133 def get_file_and_fobject(path):
134     fh, fname = get_file(path)
135     with os.fdopen(fh) as fo:
136         yield fname, fo
137
138 # Wrappers that build upon existing values
139
140 def as_wrapper(call, fn, closer=None):
141     @contextlib.contextmanager
142     def f(path):
143         with call(path) as r:
144             val = fn(r)
145             yield val
146             if closer is not None:
147                 closer(val)
148     return f
149
150 def as_bytes(call):
151     return as_wrapper(call, lambda r: r.encode())
152
153 def as_fspath(call):
154     return as_wrapper(call, pathlib.PurePath)
155
156 def as_iostream(call):
157     opener = lambda f: io.open(f, "r")
158     closer = lambda r: r.close()
159     return as_wrapper(call, opener, closer)
160
161 NOT_BEFORE_36 = pytest.mark.xfail(condition="sys.version_info < (3,6)",
162                                   strict=True)
163 NOT_PYPY = pytest.mark.xfail(condition="platform.python_implementation() == 'PyPy'",
164                                   strict=False)
165
166 require_acl_from_mode = pytest.mark.skipif("not HAS_ACL_FROM_MODE")
167 require_acl_check = pytest.mark.skipif("not HAS_ACL_CHECK")
168 require_acl_entry = pytest.mark.skipif("not HAS_ACL_ENTRY")
169 require_extended_check = pytest.mark.skipif("not HAS_EXTENDED_CHECK")
170 require_equiv_mode = pytest.mark.skipif("not HAS_EQUIV_MODE")
171
172 class TestLoad:
173     """Load/create tests"""
174     def test_from_file(self, testdir):
175         """Test loading ACLs from a file"""
176         _, fname = get_file(testdir)
177         acl1 = posix1e.ACL(file=fname)
178         assert acl1.valid()
179
180     def test_from_dir(self, testdir):
181         """Test loading ACLs from a directory"""
182         with get_dir(testdir) as dname:
183           acl1 = posix1e.ACL(file=dname)
184           acl2 = posix1e.ACL(filedef=dname)
185           assert acl1.valid()
186         # default ACLs might or might not be valid; missing ones are
187         # not valid, so we don't test acl2 for validity
188
189     def test_from_fd(self, testdir):
190         """Test loading ACLs from a file descriptor"""
191         fd, _ = get_file(testdir)
192         acl1 = posix1e.ACL(fd=fd)
193         assert acl1.valid()
194
195     def test_from_empty_invalid(self):
196         """Test creating an empty ACL"""
197         acl1 = posix1e.ACL()
198         assert not acl1.valid()
199
200     def test_from_text(self):
201         """Test creating an ACL from text"""
202         acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
203         assert acl1.valid()
204
205     def test_from_acl(self):
206         """Test creating an ACL from an existing ACL"""
207         acl1 = posix1e.ACL()
208         acl2 = posix1e.ACL(acl=acl1)
209         assert acl1 == acl2
210
211     def test_invalid_creation_params(self, testdir):
212         """Test that creating an ACL from multiple objects fails"""
213         fd, _ = get_file(testdir)
214         with pytest.raises(ValueError):
215           posix1e.ACL(text=BASIC_ACL_TEXT, fd=fd)
216
217     def test_invalid_value_creation(self):
218         """Test that creating an ACL from wrong specification fails"""
219         with pytest.raises(EnvironmentError):
220           posix1e.ACL(text="foobar")
221         with pytest.raises(TypeError):
222           posix1e.ACL(foo="bar")
223
224     def test_double_init(self):
225         acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
226         assert acl1.valid()
227         acl1.__init__(text=BASIC_ACL_TEXT)
228         assert acl1.valid()
229
230 class TestAclExtensions:
231     """ACL extensions checks"""
232
233     @require_acl_from_mode
234     def test_from_mode(self):
235         """Test loading ACLs from an octal mode"""
236         acl1 = posix1e.ACL(mode=0o644)
237         assert acl1.valid()
238
239     @require_acl_check
240     def test_acl_check(self):
241         """Test the acl_check method"""
242         acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
243         assert not acl1.check()
244         acl2 = posix1e.ACL()
245         assert acl2.check()
246
247     @require_extended_check
248     def test_extended(self, testdir):
249         """Test the acl_extended function"""
250         fd, fname = get_file(testdir)
251         basic_acl = posix1e.ACL(text=BASIC_ACL_TEXT)
252         basic_acl.applyto(fd)
253         for item in fd, fname:
254             assert not has_extended(item)
255         enhanced_acl = posix1e.ACL(text="u::rw,g::-,o::-,u:root:rw,mask::r")
256         assert enhanced_acl.valid()
257         enhanced_acl.applyto(fd)
258         for item in fd, fname:
259             assert has_extended(item)
260
261     @require_extended_check
262     def test_extended_arg_handling(self):
263       with pytest.raises(TypeError):
264         has_extended()
265       with pytest.raises(TypeError):
266         has_extended(object())
267
268     @require_equiv_mode
269     def test_equiv_mode(self):
270         """Test the equiv_mode function"""
271         if HAS_ACL_FROM_MODE:
272             for mode in 0o644, 0o755:
273                 acl = posix1e.ACL(mode=mode)
274                 assert acl.equiv_mode() == mode
275         acl = posix1e.ACL(text="u::rw,g::r,o::r")
276         assert acl.equiv_mode() == 0o644
277         acl = posix1e.ACL(text="u::rx,g::-,o::-")
278         assert acl.equiv_mode() == 0o500
279
280     @require_acl_check
281     def test_to_any_text(self):
282         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
283         assert b"u::" in \
284           acl.to_any_text(options=posix1e.TEXT_ABBREVIATE)
285         assert b"user::" in acl.to_any_text()
286
287     @require_acl_check
288     def test_to_any_text_wrong_args(self):
289         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
290         with pytest.raises(TypeError):
291           acl.to_any_text(foo="bar")
292
293
294     @require_acl_check
295     def test_rich_compare(self):
296         acl1 = posix1e.ACL(text="u::rw,g::r,o::r")
297         acl2 = posix1e.ACL(acl=acl1)
298         acl3 = posix1e.ACL(text="u::rw,g::rw,o::r")
299         assert acl1 == acl2
300         assert acl1 != acl3
301         with pytest.raises(TypeError):
302           acl1 < acl2
303         with pytest.raises(TypeError):
304           acl1 >= acl3
305         assert acl1 != True
306         assert not (acl1 == 1)
307         with pytest.raises(TypeError):
308           acl1 > True
309
310     def test_apply_to_with_wrong_object(self):
311         acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
312         assert acl1.valid()
313         with pytest.raises(TypeError):
314           acl1.applyto(object())
315         with pytest.raises(TypeError):
316           acl1.applyto(object(), object())
317
318     @require_acl_entry
319     def test_acl_iterator(self):
320         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
321         for entry in acl:
322             assert entry.parent is acl
323
324
325 class TestWrite:
326     """Write tests"""
327
328     def test_delete_default(self, testdir):
329         """Test removing the default ACL"""
330         with get_dir(testdir) as dname:
331           posix1e.delete_default(dname)
332
333     @NOT_PYPY
334     def test_delete_default_wrong_arg(self):
335         with pytest.raises(TypeError):
336           posix1e.delete_default(object())
337
338     def test_reapply(self, testdir):
339         """Test re-applying an ACL"""
340         fd, fname = get_file(testdir)
341         acl1 = posix1e.ACL(fd=fd)
342         acl1.applyto(fd)
343         acl1.applyto(fname)
344         with get_dir(testdir) as dname:
345           acl2 = posix1e.ACL(file=fname)
346           acl2.applyto(dname)
347
348
349
350 @require_acl_entry
351 class TestModification:
352     """ACL modification tests"""
353
354     def checkRef(self, obj):
355         """Checks if a given obj has a 'sane' refcount"""
356         if platform.python_implementation() == "PyPy":
357             return
358         ref_cnt = sys.getrefcount(obj)
359         # FIXME: hardcoded value for the max ref count... but I've
360         # seen it overflow on bad reference counting, so it's better
361         # to be safe
362         if ref_cnt < 2 or ref_cnt > 1024:
363             pytest.fail("Wrong reference count, expected 2-1024 and got %d" %
364                         ref_cnt)
365
366     def test_str(self):
367         """Test str() of an ACL."""
368         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
369         str_acl = str(acl)
370         self.checkRef(str_acl)
371
372     def test_append(self):
373         """Test append a new Entry to the ACL"""
374         acl = posix1e.ACL()
375         e = acl.append()
376         e.tag_type = posix1e.ACL_OTHER
377         ignore_ioerror(errno.EINVAL, acl.calc_mask)
378         str_format = str(e)
379         self.checkRef(str_format)
380         e2 = acl.append(e)
381         ignore_ioerror(errno.EINVAL, acl.calc_mask)
382         assert not acl.valid()
383
384     def test_wrong_append(self):
385         """Test append a new Entry to the ACL based on wrong object type"""
386         acl = posix1e.ACL()
387         with pytest.raises(TypeError):
388           acl.append(object())
389
390     def test_entry_creation(self):
391         acl = posix1e.ACL()
392         e = posix1e.Entry(acl)
393         ignore_ioerror(errno.EINVAL, acl.calc_mask)
394         str_format = str(e)
395         self.checkRef(str_format)
396
397     def test_entry_failed_creation(self):
398         # Checks for partial initialisation and deletion on error
399         # path.
400         with pytest.raises(TypeError):
401           posix1e.Entry(object())
402
403     def test_delete(self):
404         """Test delete Entry from the ACL"""
405         acl = posix1e.ACL()
406         e = acl.append()
407         e.tag_type = posix1e.ACL_OTHER
408         ignore_ioerror(errno.EINVAL, acl.calc_mask)
409         acl.delete_entry(e)
410         ignore_ioerror(errno.EINVAL, acl.calc_mask)
411
412     def test_double_delete(self):
413         """Test delete Entry from the ACL"""
414         # This is not entirely valid/correct, since the entry object
415         # itself is invalid after the first deletion, so we're
416         # actually testing deleting an invalid object, not a
417         # non-existing entry...
418         acl = posix1e.ACL()
419         e = acl.append()
420         e.tag_type = posix1e.ACL_OTHER
421         ignore_ioerror(errno.EINVAL, acl.calc_mask)
422         acl.delete_entry(e)
423         ignore_ioerror(errno.EINVAL, acl.calc_mask)
424         with pytest.raises(EnvironmentError):
425           acl.delete_entry(e)
426
427     # This currently fails as this deletion seems to be accepted :/
428     @pytest.mark.xfail(reason="Entry deletion is unreliable")
429     def testDeleteInvalidEntry(self):
430         """Test delete foreign Entry from the ACL"""
431         acl1 = posix1e.ACL()
432         acl2 = posix1e.ACL()
433         e = acl1.append()
434         e.tag_type = posix1e.ACL_OTHER
435         ignore_ioerror(errno.EINVAL, acl1.calc_mask)
436         with pytest.raises(EnvironmentError):
437           acl2.delete_entry(e)
438
439     def test_delete_invalid_object(self):
440         """Test delete a non-Entry from the ACL"""
441         acl = posix1e.ACL()
442         with pytest.raises(TypeError):
443           acl.delete_entry(object())
444
445     def test_double_entries(self):
446         """Test double entries"""
447         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
448         assert acl.valid()
449         for tag_type in (posix1e.ACL_USER_OBJ, posix1e.ACL_GROUP_OBJ,
450                          posix1e.ACL_OTHER):
451             e = acl.append()
452             e.tag_type = tag_type
453             e.permset.clear()
454             assert not acl.valid(), ("ACL containing duplicate entries"
455                                      " should not be valid")
456             acl.delete_entry(e)
457
458     def test_multiple_good_entries(self):
459         """Test multiple valid entries"""
460         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
461         assert acl.valid()
462         for tag_type in (posix1e.ACL_USER,
463                          posix1e.ACL_GROUP):
464             for obj_id in range(5):
465                 e = acl.append()
466                 e.tag_type = tag_type
467                 e.qualifier = obj_id
468                 e.permset.clear()
469                 acl.calc_mask()
470                 assert acl.valid(), ("ACL should be able to hold multiple"
471                                      " user/group entries")
472
473     def test_multiple_bad_entries(self):
474         """Test multiple invalid entries"""
475         for tag_type in (posix1e.ACL_USER,
476                          posix1e.ACL_GROUP):
477             acl = posix1e.ACL(text=BASIC_ACL_TEXT)
478             assert acl.valid()
479             e1 = acl.append()
480             e1.tag_type = tag_type
481             e1.qualifier = 0
482             e1.permset.clear()
483             acl.calc_mask()
484             assert acl.valid(), ("ACL should be able to add a"
485                                  " user/group entry")
486             e2 = acl.append()
487             e2.tag_type = tag_type
488             e2.qualifier = 0
489             e2.permset.clear()
490             ignore_ioerror(errno.EINVAL, acl.calc_mask)
491             assert not acl.valid(), ("ACL should not validate when"
492                                      " containing two duplicate entries")
493             acl.delete_entry(e1)
494             # FreeBSD trips over itself here and can't delete the
495             # entry, even though it still exists.
496             ignore_ioerror(errno.EINVAL, acl.delete_entry, e2)
497
498     def test_copy(self):
499         acl = ACL()
500         e1 = acl.append()
501         e1.tag_type = ACL_USER
502         p1 = e1.permset
503         p1.clear()
504         p1.read = True
505         p1.write = True
506         e2 = acl.append()
507         e2.tag_type = ACL_GROUP
508         p2 = e2.permset
509         p2.clear()
510         p2.read = True
511         assert not p2.write
512         e2.copy(e1)
513         assert p2.write
514         assert e1.tag_type == e2.tag_type
515
516     def test_copy_wrong_arg(self):
517         acl = ACL()
518         e = acl.append()
519         with pytest.raises(TypeError):
520           e.copy(object())
521
522     def test_set_permset(self):
523         acl = ACL()
524         e1 = acl.append()
525         e1.tag_type = ACL_USER
526         p1 = e1.permset
527         p1.clear()
528         p1.read = True
529         p1.write = True
530         e2 = acl.append()
531         e2.tag_type = ACL_GROUP
532         p2 = e2.permset
533         p2.clear()
534         p2.read = True
535         assert not p2.write
536         e2.permset = p1
537         assert e2.permset.write
538         assert e2.tag_type == ACL_GROUP
539
540     def test_set_permset_wrong_arg(self):
541         acl = ACL()
542         e = acl.append()
543         with pytest.raises(TypeError):
544           e.permset = object()
545
546     def test_permset_creation(self):
547         acl = ACL()
548         e = acl.append()
549         p1 = e.permset
550         p2 = Permset(e)
551         #self.assertEqual(p1, p2)
552
553     def test_permset_creation_wrong_arg(self):
554         with pytest.raises(TypeError):
555           Permset(object())
556
557     def test_permset(self):
558         """Test permissions"""
559         acl = posix1e.ACL()
560         e = acl.append()
561         ps = e.permset
562         ps.clear()
563         str_ps = str(ps)
564         self.checkRef(str_ps)
565         for perm in PERMSETS:
566             str_ps = str(ps)
567             txt = PERMSETS[perm][0]
568             self.checkRef(str_ps)
569             assert not ps.test(perm), ("Empty permission set should not"
570                                        " have permission '%s'" % txt)
571             ps.add(perm)
572             assert ps.test(perm), ("Permission '%s' should exist"
573                                    " after addition" % txt)
574             str_ps = str(ps)
575             self.checkRef(str_ps)
576             ps.delete(perm)
577             assert not ps.test(perm), ("Permission '%s' should not exist"
578                                        " after deletion" % txt)
579
580     def test_permset_via_accessors(self):
581         """Test permissions"""
582         acl = posix1e.ACL()
583         e = acl.append()
584         ps = e.permset
585         ps.clear()
586         str_ps = str(ps)
587         self.checkRef(str_ps)
588         def getter(perm):
589             return PERMSETS[perm][1].__get__(ps)
590         def setter(parm, value):
591             return PERMSETS[perm][1].__set__(ps, value)
592         for perm in PERMSETS:
593             str_ps = str(ps)
594             self.checkRef(str_ps)
595             txt = PERMSETS[perm][0]
596             assert not getter(perm), ("Empty permission set should not"
597                                       " have permission '%s'" % txt)
598             setter(perm, True)
599             assert ps.test(perm), ("Permission '%s' should exist"
600                                    " after addition" % txt)
601             assert getter(perm), ("Permission '%s' should exist"
602                                   " after addition" % txt)
603             str_ps = str(ps)
604             self.checkRef(str_ps)
605             setter(perm, False)
606             assert not ps.test(perm), ("Permission '%s' should not exist"
607                                        " after deletion" % txt)
608             assert not getter(perm), ("Permission '%s' should not exist"
609                                       " after deletion" % txt)
610
611     def test_permset_invalid_type(self):
612         acl = posix1e.ACL()
613         e = acl.append()
614         ps = e.permset
615         ps.clear()
616         with pytest.raises(TypeError):
617           ps.add("foobar")
618         with pytest.raises(TypeError):
619           ps.delete("foobar")
620         with pytest.raises(TypeError):
621           ps.test("foobar")
622         with pytest.raises(ValueError):
623           ps.write = object()
624
625     def test_qualifier_values(self):
626         """Tests qualifier correct store/retrieval"""
627         acl = posix1e.ACL()
628         e = acl.append()
629         # work around deprecation warnings
630         for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
631             qualifier = 1
632             e.tag_type = tag
633             while True:
634                 if tag == posix1e.ACL_USER:
635                     regex = re.compile("user with uid %d" % qualifier)
636                 else:
637                     regex = re.compile("group with gid %d" % qualifier)
638                 try:
639                     e.qualifier = qualifier
640                 except OverflowError:
641                     # reached overflow condition, break
642                     break
643                 assert e.qualifier == qualifier
644                 assert regex.search(str(e)) is not None
645                 qualifier *= 2
646
647     def test_qualifier_overflow(self):
648         """Tests qualifier overflow handling"""
649         acl = posix1e.ACL()
650         e = acl.append()
651         qualifier = sys.maxsize * 2
652         for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
653             e.tag_type = tag
654             with pytest.raises(OverflowError):
655                 e.qualifier = qualifier
656
657     def test_negative_qualifier(self):
658         """Tests negative qualifier handling"""
659         # Note: this presumes that uid_t/gid_t in C are unsigned...
660         acl = posix1e.ACL()
661         e = acl.append()
662         for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
663             e.tag_type = tag
664             for qualifier in [-10, -5, -1]:
665                 with pytest.raises(OverflowError):
666                     e.qualifier = qualifier
667
668     def test_invalid_qualifier(self):
669         """Tests invalid qualifier handling"""
670         acl = posix1e.ACL()
671         e = acl.append()
672         with pytest.raises(TypeError):
673           e.qualifier = object()
674         with pytest.raises((TypeError, AttributeError)):
675           del e.qualifier
676
677     def test_qualifier_on_wrong_tag(self):
678         """Tests qualifier setting on wrong tag"""
679         acl = posix1e.ACL()
680         e = acl.append()
681         e.tag_type = posix1e.ACL_OTHER
682         with pytest.raises(TypeError):
683           e.qualifier = 1
684         with pytest.raises(TypeError):
685           e.qualifier
686
687     @pytest.mark.parametrize("tag", ALL_TAG_VALUES, ids=ALL_TAG_DESCS)
688     def test_tag_types(self, tag):
689         """Tests tag type correct set/get"""
690         acl = posix1e.ACL()
691         e = acl.append()
692         e.tag_type = tag
693         assert e.tag_type == tag
694         # check we can show all tag types without breaking
695         assert str(e)
696
697     @pytest.mark.parametrize("src_tag", ALL_TAG_VALUES, ids=ALL_TAG_DESCS)
698     @pytest.mark.parametrize("dst_tag", ALL_TAG_VALUES, ids=ALL_TAG_DESCS)
699     def test_tag_overwrite(self, src_tag, dst_tag):
700         """Tests tag type correct set/get"""
701         acl = posix1e.ACL()
702         e = acl.append()
703         e.tag_type = src_tag
704         assert e.tag_type == src_tag
705         assert str(e)
706         e.tag_type = dst_tag
707         assert e.tag_type == dst_tag
708         assert str(e)
709
710     def test_invalid_tags(self):
711         """Tests tag type incorrect set/get"""
712         acl = posix1e.ACL()
713         e = acl.append()
714         with pytest.raises(TypeError):
715           e.tag_type = object()
716         e.tag_type = posix1e.ACL_USER_OBJ
717         # For some reason, PyPy raises AttributeError. Strange...
718         with pytest.raises((TypeError, AttributeError)):
719           del e.tag_type
720
721     def test_tag_wrong_overwrite(self):
722         acl = posix1e.ACL()
723         e = acl.append()
724         e.tag_type = posix1e.ACL_USER_OBJ
725         tag = max(ALL_TAG_VALUES) + 1
726         with pytest.raises(EnvironmentError):
727           e.tag_type = tag
728         # Check tag is still valid.
729         assert e.tag_type == posix1e.ACL_USER_OBJ
730
731 if __name__ == "__main__":
732     unittest.main()