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