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