]> git.k1024.org Git - pylibacl.git/blob - tests/test_acls.py
Add pathlib support in apply_to and has_extended
[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_applyto_extended(self, subject):
311         """Test the acl_extended function"""
312         basic_acl = posix1e.ACL(text=BASIC_ACL_TEXT)
313         basic_acl.applyto(subject)
314         assert not has_extended(subject)
315         enhanced_acl = posix1e.ACL(text="u::rw,g::-,o::-,u:root:rw,mask::r")
316         assert enhanced_acl.valid()
317         enhanced_acl.applyto(subject)
318         assert has_extended(subject)
319
320     @require_extended_check
321     @pytest.mark.parametrize(
322         "gen", [ get_file_and_symlink, get_file_and_fobject ])
323     def test_applyto_extended_mixed(self, testdir, gen):
324         """Test the acl_extended function"""
325         with gen(testdir) as (a, b):
326             basic_acl = posix1e.ACL(text=BASIC_ACL_TEXT)
327             basic_acl.applyto(a)
328             for item in a, b:
329                 assert not has_extended(item)
330             enhanced_acl = posix1e.ACL(text="u::rw,g::-,o::-,u:root:rw,mask::r")
331             assert enhanced_acl.valid()
332             enhanced_acl.applyto(b)
333             for item in a, b:
334                 assert has_extended(item)
335
336     @require_extended_check
337     def test_extended_arg_handling(self):
338       with pytest.raises(TypeError):
339         has_extended()
340       with pytest.raises(TypeError):
341         has_extended(object())
342
343     @require_equiv_mode
344     def test_equiv_mode(self):
345         """Test the equiv_mode function"""
346         if HAS_ACL_FROM_MODE:
347             for mode in 0o644, 0o755:
348                 acl = posix1e.ACL(mode=mode)
349                 assert acl.equiv_mode() == mode
350         acl = posix1e.ACL(text="u::rw,g::r,o::r")
351         assert acl.equiv_mode() == 0o644
352         acl = posix1e.ACL(text="u::rx,g::-,o::-")
353         assert acl.equiv_mode() == 0o500
354
355     @require_acl_check
356     def test_to_any_text(self):
357         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
358         assert b"u::" in \
359           acl.to_any_text(options=posix1e.TEXT_ABBREVIATE)
360         assert b"user::" in acl.to_any_text()
361
362     @require_acl_check
363     def test_to_any_text_wrong_args(self):
364         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
365         with pytest.raises(TypeError):
366           acl.to_any_text(foo="bar")
367
368
369     @require_acl_check
370     def test_rich_compare(self):
371         acl1 = posix1e.ACL(text="u::rw,g::r,o::r")
372         acl2 = posix1e.ACL(acl=acl1)
373         acl3 = posix1e.ACL(text="u::rw,g::rw,o::r")
374         assert acl1 == acl2
375         assert acl1 != acl3
376         with pytest.raises(TypeError):
377           acl1 < acl2
378         with pytest.raises(TypeError):
379           acl1 >= acl3
380         assert acl1 != True
381         assert not (acl1 == 1)
382         with pytest.raises(TypeError):
383           acl1 > True
384
385     def test_apply_to_with_wrong_object(self):
386         acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
387         assert acl1.valid()
388         with pytest.raises(TypeError):
389           acl1.applyto(object())
390         with pytest.raises(TypeError):
391           acl1.applyto(object(), object())
392
393     @require_acl_entry
394     def test_acl_iterator(self):
395         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
396         for entry in acl:
397             assert entry.parent is acl
398
399
400 class TestWrite:
401     """Write tests"""
402
403     def test_delete_default(self, testdir):
404         """Test removing the default ACL"""
405         with get_dir(testdir) as dname:
406           posix1e.delete_default(dname)
407
408     @NOT_PYPY
409     def test_delete_default_wrong_arg(self):
410         with pytest.raises(TypeError):
411           posix1e.delete_default(object())
412
413     def test_reapply(self, testdir):
414         """Test re-applying an ACL"""
415         fd, fname = get_file(testdir)
416         acl1 = posix1e.ACL(fd=fd)
417         acl1.applyto(fd)
418         acl1.applyto(fname)
419         with get_dir(testdir) as dname:
420           acl2 = posix1e.ACL(file=fname)
421           acl2.applyto(dname)
422
423
424
425 @require_acl_entry
426 class TestModification:
427     """ACL modification tests"""
428
429     def checkRef(self, obj):
430         """Checks if a given obj has a 'sane' refcount"""
431         if platform.python_implementation() == "PyPy":
432             return
433         ref_cnt = sys.getrefcount(obj)
434         # FIXME: hardcoded value for the max ref count... but I've
435         # seen it overflow on bad reference counting, so it's better
436         # to be safe
437         if ref_cnt < 2 or ref_cnt > 1024:
438             pytest.fail("Wrong reference count, expected 2-1024 and got %d" %
439                         ref_cnt)
440
441     def test_str(self):
442         """Test str() of an ACL."""
443         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
444         str_acl = str(acl)
445         self.checkRef(str_acl)
446
447     def test_append(self):
448         """Test append a new Entry to the ACL"""
449         acl = posix1e.ACL()
450         e = acl.append()
451         e.tag_type = posix1e.ACL_OTHER
452         ignore_ioerror(errno.EINVAL, acl.calc_mask)
453         str_format = str(e)
454         self.checkRef(str_format)
455         e2 = acl.append(e)
456         ignore_ioerror(errno.EINVAL, acl.calc_mask)
457         assert not acl.valid()
458
459     def test_wrong_append(self):
460         """Test append a new Entry to the ACL based on wrong object type"""
461         acl = posix1e.ACL()
462         with pytest.raises(TypeError):
463           acl.append(object())
464
465     def test_entry_creation(self):
466         acl = posix1e.ACL()
467         e = posix1e.Entry(acl)
468         ignore_ioerror(errno.EINVAL, acl.calc_mask)
469         str_format = str(e)
470         self.checkRef(str_format)
471
472     def test_entry_failed_creation(self):
473         # Checks for partial initialisation and deletion on error
474         # path.
475         with pytest.raises(TypeError):
476           posix1e.Entry(object())
477
478     def test_delete(self):
479         """Test delete Entry from the ACL"""
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
487     def test_double_delete(self):
488         """Test delete Entry from the ACL"""
489         # This is not entirely valid/correct, since the entry object
490         # itself is invalid after the first deletion, so we're
491         # actually testing deleting an invalid object, not a
492         # non-existing entry...
493         acl = posix1e.ACL()
494         e = acl.append()
495         e.tag_type = posix1e.ACL_OTHER
496         ignore_ioerror(errno.EINVAL, acl.calc_mask)
497         acl.delete_entry(e)
498         ignore_ioerror(errno.EINVAL, acl.calc_mask)
499         with pytest.raises(EnvironmentError):
500           acl.delete_entry(e)
501
502     # This currently fails as this deletion seems to be accepted :/
503     @pytest.mark.xfail(reason="Entry deletion is unreliable")
504     def testDeleteInvalidEntry(self):
505         """Test delete foreign Entry from the ACL"""
506         acl1 = posix1e.ACL()
507         acl2 = posix1e.ACL()
508         e = acl1.append()
509         e.tag_type = posix1e.ACL_OTHER
510         ignore_ioerror(errno.EINVAL, acl1.calc_mask)
511         with pytest.raises(EnvironmentError):
512           acl2.delete_entry(e)
513
514     def test_delete_invalid_object(self):
515         """Test delete a non-Entry from the ACL"""
516         acl = posix1e.ACL()
517         with pytest.raises(TypeError):
518           acl.delete_entry(object())
519
520     def test_double_entries(self):
521         """Test double entries"""
522         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
523         assert acl.valid()
524         for tag_type in (posix1e.ACL_USER_OBJ, posix1e.ACL_GROUP_OBJ,
525                          posix1e.ACL_OTHER):
526             e = acl.append()
527             e.tag_type = tag_type
528             e.permset.clear()
529             assert not acl.valid(), ("ACL containing duplicate entries"
530                                      " should not be valid")
531             acl.delete_entry(e)
532
533     def test_multiple_good_entries(self):
534         """Test multiple valid entries"""
535         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
536         assert acl.valid()
537         for tag_type in (posix1e.ACL_USER,
538                          posix1e.ACL_GROUP):
539             for obj_id in range(5):
540                 e = acl.append()
541                 e.tag_type = tag_type
542                 e.qualifier = obj_id
543                 e.permset.clear()
544                 acl.calc_mask()
545                 assert acl.valid(), ("ACL should be able to hold multiple"
546                                      " user/group entries")
547
548     def test_multiple_bad_entries(self):
549         """Test multiple invalid entries"""
550         for tag_type in (posix1e.ACL_USER,
551                          posix1e.ACL_GROUP):
552             acl = posix1e.ACL(text=BASIC_ACL_TEXT)
553             assert acl.valid()
554             e1 = acl.append()
555             e1.tag_type = tag_type
556             e1.qualifier = 0
557             e1.permset.clear()
558             acl.calc_mask()
559             assert acl.valid(), ("ACL should be able to add a"
560                                  " user/group entry")
561             e2 = acl.append()
562             e2.tag_type = tag_type
563             e2.qualifier = 0
564             e2.permset.clear()
565             ignore_ioerror(errno.EINVAL, acl.calc_mask)
566             assert not acl.valid(), ("ACL should not validate when"
567                                      " containing two duplicate entries")
568             acl.delete_entry(e1)
569             # FreeBSD trips over itself here and can't delete the
570             # entry, even though it still exists.
571             ignore_ioerror(errno.EINVAL, acl.delete_entry, e2)
572
573     def test_copy(self):
574         acl = ACL()
575         e1 = acl.append()
576         e1.tag_type = ACL_USER
577         p1 = e1.permset
578         p1.clear()
579         p1.read = True
580         p1.write = True
581         e2 = acl.append()
582         e2.tag_type = ACL_GROUP
583         p2 = e2.permset
584         p2.clear()
585         p2.read = True
586         assert not p2.write
587         e2.copy(e1)
588         assert p2.write
589         assert e1.tag_type == e2.tag_type
590
591     def test_copy_wrong_arg(self):
592         acl = ACL()
593         e = acl.append()
594         with pytest.raises(TypeError):
595           e.copy(object())
596
597     def test_set_permset(self):
598         acl = ACL()
599         e1 = acl.append()
600         e1.tag_type = ACL_USER
601         p1 = e1.permset
602         p1.clear()
603         p1.read = True
604         p1.write = True
605         e2 = acl.append()
606         e2.tag_type = ACL_GROUP
607         p2 = e2.permset
608         p2.clear()
609         p2.read = True
610         assert not p2.write
611         e2.permset = p1
612         assert e2.permset.write
613         assert e2.tag_type == ACL_GROUP
614
615     def test_set_permset_wrong_arg(self):
616         acl = ACL()
617         e = acl.append()
618         with pytest.raises(TypeError):
619           e.permset = object()
620
621     def test_permset_creation(self):
622         acl = ACL()
623         e = acl.append()
624         p1 = e.permset
625         p2 = Permset(e)
626         #self.assertEqual(p1, p2)
627
628     def test_permset_creation_wrong_arg(self):
629         with pytest.raises(TypeError):
630           Permset(object())
631
632     def test_permset(self):
633         """Test permissions"""
634         acl = posix1e.ACL()
635         e = acl.append()
636         ps = e.permset
637         ps.clear()
638         str_ps = str(ps)
639         self.checkRef(str_ps)
640         for perm in PERMSETS:
641             str_ps = str(ps)
642             txt = PERMSETS[perm][0]
643             self.checkRef(str_ps)
644             assert not ps.test(perm), ("Empty permission set should not"
645                                        " have permission '%s'" % txt)
646             ps.add(perm)
647             assert ps.test(perm), ("Permission '%s' should exist"
648                                    " after addition" % txt)
649             str_ps = str(ps)
650             self.checkRef(str_ps)
651             ps.delete(perm)
652             assert not ps.test(perm), ("Permission '%s' should not exist"
653                                        " after deletion" % txt)
654
655     def test_permset_via_accessors(self):
656         """Test permissions"""
657         acl = posix1e.ACL()
658         e = acl.append()
659         ps = e.permset
660         ps.clear()
661         str_ps = str(ps)
662         self.checkRef(str_ps)
663         def getter(perm):
664             return PERMSETS[perm][1].__get__(ps)
665         def setter(parm, value):
666             return PERMSETS[perm][1].__set__(ps, value)
667         for perm in PERMSETS:
668             str_ps = str(ps)
669             self.checkRef(str_ps)
670             txt = PERMSETS[perm][0]
671             assert not getter(perm), ("Empty permission set should not"
672                                       " have permission '%s'" % txt)
673             setter(perm, True)
674             assert ps.test(perm), ("Permission '%s' should exist"
675                                    " after addition" % txt)
676             assert getter(perm), ("Permission '%s' should exist"
677                                   " after addition" % txt)
678             str_ps = str(ps)
679             self.checkRef(str_ps)
680             setter(perm, False)
681             assert not ps.test(perm), ("Permission '%s' should not exist"
682                                        " after deletion" % txt)
683             assert not getter(perm), ("Permission '%s' should not exist"
684                                       " after deletion" % txt)
685
686     def test_permset_invalid_type(self):
687         acl = posix1e.ACL()
688         e = acl.append()
689         ps = e.permset
690         ps.clear()
691         with pytest.raises(TypeError):
692           ps.add("foobar")
693         with pytest.raises(TypeError):
694           ps.delete("foobar")
695         with pytest.raises(TypeError):
696           ps.test("foobar")
697         with pytest.raises(ValueError):
698           ps.write = object()
699
700     def test_qualifier_values(self):
701         """Tests qualifier correct store/retrieval"""
702         acl = posix1e.ACL()
703         e = acl.append()
704         # work around deprecation warnings
705         for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
706             qualifier = 1
707             e.tag_type = tag
708             while True:
709                 if tag == posix1e.ACL_USER:
710                     regex = re.compile("user with uid %d" % qualifier)
711                 else:
712                     regex = re.compile("group with gid %d" % qualifier)
713                 try:
714                     e.qualifier = qualifier
715                 except OverflowError:
716                     # reached overflow condition, break
717                     break
718                 assert e.qualifier == qualifier
719                 assert regex.search(str(e)) is not None
720                 qualifier *= 2
721
722     def test_qualifier_overflow(self):
723         """Tests qualifier overflow handling"""
724         acl = posix1e.ACL()
725         e = acl.append()
726         qualifier = sys.maxsize * 2
727         for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
728             e.tag_type = tag
729             with pytest.raises(OverflowError):
730                 e.qualifier = qualifier
731
732     def test_negative_qualifier(self):
733         """Tests negative qualifier handling"""
734         # Note: this presumes that uid_t/gid_t in C are unsigned...
735         acl = posix1e.ACL()
736         e = acl.append()
737         for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
738             e.tag_type = tag
739             for qualifier in [-10, -5, -1]:
740                 with pytest.raises(OverflowError):
741                     e.qualifier = qualifier
742
743     def test_invalid_qualifier(self):
744         """Tests invalid qualifier handling"""
745         acl = posix1e.ACL()
746         e = acl.append()
747         with pytest.raises(TypeError):
748           e.qualifier = object()
749         with pytest.raises((TypeError, AttributeError)):
750           del e.qualifier
751
752     def test_qualifier_on_wrong_tag(self):
753         """Tests qualifier setting on wrong tag"""
754         acl = posix1e.ACL()
755         e = acl.append()
756         e.tag_type = posix1e.ACL_OTHER
757         with pytest.raises(TypeError):
758           e.qualifier = 1
759         with pytest.raises(TypeError):
760           e.qualifier
761
762     @pytest.mark.parametrize("tag", ALL_TAG_VALUES, ids=ALL_TAG_DESCS)
763     def test_tag_types(self, tag):
764         """Tests tag type correct set/get"""
765         acl = posix1e.ACL()
766         e = acl.append()
767         e.tag_type = tag
768         assert e.tag_type == tag
769         # check we can show all tag types without breaking
770         assert str(e)
771
772     @pytest.mark.parametrize("src_tag", ALL_TAG_VALUES, ids=ALL_TAG_DESCS)
773     @pytest.mark.parametrize("dst_tag", ALL_TAG_VALUES, ids=ALL_TAG_DESCS)
774     def test_tag_overwrite(self, src_tag, dst_tag):
775         """Tests tag type correct set/get"""
776         acl = posix1e.ACL()
777         e = acl.append()
778         e.tag_type = src_tag
779         assert e.tag_type == src_tag
780         assert str(e)
781         e.tag_type = dst_tag
782         assert e.tag_type == dst_tag
783         assert str(e)
784
785     def test_invalid_tags(self):
786         """Tests tag type incorrect set/get"""
787         acl = posix1e.ACL()
788         e = acl.append()
789         with pytest.raises(TypeError):
790           e.tag_type = object()
791         e.tag_type = posix1e.ACL_USER_OBJ
792         # For some reason, PyPy raises AttributeError. Strange...
793         with pytest.raises((TypeError, AttributeError)):
794           del e.tag_type
795
796     def test_tag_wrong_overwrite(self):
797         acl = posix1e.ACL()
798         e = acl.append()
799         e.tag_type = posix1e.ACL_USER_OBJ
800         tag = max(ALL_TAG_VALUES) + 1
801         with pytest.raises(EnvironmentError):
802           e.tag_type = tag
803         # Check tag is still valid.
804         assert e.tag_type == posix1e.ACL_USER_OBJ
805
806 if __name__ == "__main__":
807     unittest.main()