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