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