]> git.k1024.org Git - pylibacl.git/blob - tests/test_acls.py
Include filename in exceptions raised in delete_default
[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     def test_delete_default_fail(self, testdir):
449         """Test removing the default ACL"""
450         with get_file_name(testdir) as fname:
451             with pytest.raises(IOError, match="no-such-file"):
452                 posix1e.delete_default(fname+".no-such-file")
453
454     @NOT_PYPY
455     def test_delete_default_wrong_arg(self):
456         with pytest.raises(TypeError):
457           posix1e.delete_default(object())
458
459     def test_reapply(self, testdir):
460         """Test re-applying an ACL"""
461         fd, fname = get_file(testdir)
462         acl1 = posix1e.ACL(fd=fd)
463         acl1.applyto(fd)
464         acl1.applyto(fname)
465         with get_dir(testdir) as dname:
466           acl2 = posix1e.ACL(file=fname)
467           acl2.applyto(dname)
468
469
470
471 @require_acl_entry
472 class TestModification:
473     """ACL modification tests"""
474
475     def checkRef(self, obj):
476         """Checks if a given obj has a 'sane' refcount"""
477         if platform.python_implementation() == "PyPy":
478             return
479         ref_cnt = sys.getrefcount(obj)
480         # FIXME: hardcoded value for the max ref count... but I've
481         # seen it overflow on bad reference counting, so it's better
482         # to be safe
483         if ref_cnt < 2 or ref_cnt > 1024:
484             pytest.fail("Wrong reference count, expected 2-1024 and got %d" %
485                         ref_cnt)
486
487     def test_str(self):
488         """Test str() of an ACL."""
489         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
490         str_acl = str(acl)
491         self.checkRef(str_acl)
492
493     def test_append(self):
494         """Test append a new Entry to the ACL"""
495         acl = posix1e.ACL()
496         e = acl.append()
497         e.tag_type = posix1e.ACL_OTHER
498         ignore_ioerror(errno.EINVAL, acl.calc_mask)
499         str_format = str(e)
500         self.checkRef(str_format)
501         e2 = acl.append(e)
502         ignore_ioerror(errno.EINVAL, acl.calc_mask)
503         assert not acl.valid()
504
505     def test_wrong_append(self):
506         """Test append a new Entry to the ACL based on wrong object type"""
507         acl = posix1e.ACL()
508         with pytest.raises(TypeError):
509           acl.append(object())
510
511     def test_entry_creation(self):
512         acl = posix1e.ACL()
513         e = posix1e.Entry(acl)
514         ignore_ioerror(errno.EINVAL, acl.calc_mask)
515         str_format = str(e)
516         self.checkRef(str_format)
517
518     def test_entry_failed_creation(self):
519         # Checks for partial initialisation and deletion on error
520         # path.
521         with pytest.raises(TypeError):
522           posix1e.Entry(object())
523
524     def test_delete(self):
525         """Test delete Entry from the ACL"""
526         acl = posix1e.ACL()
527         e = acl.append()
528         e.tag_type = posix1e.ACL_OTHER
529         ignore_ioerror(errno.EINVAL, acl.calc_mask)
530         acl.delete_entry(e)
531         ignore_ioerror(errno.EINVAL, acl.calc_mask)
532
533     def test_double_delete(self):
534         """Test delete Entry from the ACL"""
535         # This is not entirely valid/correct, since the entry object
536         # itself is invalid after the first deletion, so we're
537         # actually testing deleting an invalid object, not a
538         # non-existing entry...
539         acl = posix1e.ACL()
540         e = acl.append()
541         e.tag_type = posix1e.ACL_OTHER
542         ignore_ioerror(errno.EINVAL, acl.calc_mask)
543         acl.delete_entry(e)
544         ignore_ioerror(errno.EINVAL, acl.calc_mask)
545         with pytest.raises(EnvironmentError):
546           acl.delete_entry(e)
547
548     # This currently fails as this deletion seems to be accepted :/
549     @pytest.mark.xfail(reason="Entry deletion is unreliable")
550     def testDeleteInvalidEntry(self):
551         """Test delete foreign Entry from the ACL"""
552         acl1 = posix1e.ACL()
553         acl2 = posix1e.ACL()
554         e = acl1.append()
555         e.tag_type = posix1e.ACL_OTHER
556         ignore_ioerror(errno.EINVAL, acl1.calc_mask)
557         with pytest.raises(EnvironmentError):
558           acl2.delete_entry(e)
559
560     def test_delete_invalid_object(self):
561         """Test delete a non-Entry from the ACL"""
562         acl = posix1e.ACL()
563         with pytest.raises(TypeError):
564           acl.delete_entry(object())
565
566     def test_double_entries(self):
567         """Test double entries"""
568         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
569         assert acl.valid()
570         for tag_type in (posix1e.ACL_USER_OBJ, posix1e.ACL_GROUP_OBJ,
571                          posix1e.ACL_OTHER):
572             e = acl.append()
573             e.tag_type = tag_type
574             e.permset.clear()
575             assert not acl.valid(), ("ACL containing duplicate entries"
576                                      " should not be valid")
577             acl.delete_entry(e)
578
579     def test_multiple_good_entries(self):
580         """Test multiple valid entries"""
581         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
582         assert acl.valid()
583         for tag_type in (posix1e.ACL_USER,
584                          posix1e.ACL_GROUP):
585             for obj_id in range(5):
586                 e = acl.append()
587                 e.tag_type = tag_type
588                 e.qualifier = obj_id
589                 e.permset.clear()
590                 acl.calc_mask()
591                 assert acl.valid(), ("ACL should be able to hold multiple"
592                                      " user/group entries")
593
594     def test_multiple_bad_entries(self):
595         """Test multiple invalid entries"""
596         for tag_type in (posix1e.ACL_USER,
597                          posix1e.ACL_GROUP):
598             acl = posix1e.ACL(text=BASIC_ACL_TEXT)
599             assert acl.valid()
600             e1 = acl.append()
601             e1.tag_type = tag_type
602             e1.qualifier = 0
603             e1.permset.clear()
604             acl.calc_mask()
605             assert acl.valid(), ("ACL should be able to add a"
606                                  " user/group entry")
607             e2 = acl.append()
608             e2.tag_type = tag_type
609             e2.qualifier = 0
610             e2.permset.clear()
611             ignore_ioerror(errno.EINVAL, acl.calc_mask)
612             assert not acl.valid(), ("ACL should not validate when"
613                                      " containing two duplicate entries")
614             acl.delete_entry(e1)
615             # FreeBSD trips over itself here and can't delete the
616             # entry, even though it still exists.
617             ignore_ioerror(errno.EINVAL, acl.delete_entry, e2)
618
619     def test_copy(self):
620         acl = ACL()
621         e1 = acl.append()
622         e1.tag_type = ACL_USER
623         p1 = e1.permset
624         p1.clear()
625         p1.read = True
626         p1.write = True
627         e2 = acl.append()
628         e2.tag_type = ACL_GROUP
629         p2 = e2.permset
630         p2.clear()
631         p2.read = True
632         assert not p2.write
633         e2.copy(e1)
634         assert p2.write
635         assert e1.tag_type == e2.tag_type
636
637     def test_copy_wrong_arg(self):
638         acl = ACL()
639         e = acl.append()
640         with pytest.raises(TypeError):
641           e.copy(object())
642
643     def test_set_permset(self):
644         acl = ACL()
645         e1 = acl.append()
646         e1.tag_type = ACL_USER
647         p1 = e1.permset
648         p1.clear()
649         p1.read = True
650         p1.write = True
651         e2 = acl.append()
652         e2.tag_type = ACL_GROUP
653         p2 = e2.permset
654         p2.clear()
655         p2.read = True
656         assert not p2.write
657         e2.permset = p1
658         assert e2.permset.write
659         assert e2.tag_type == ACL_GROUP
660
661     def test_set_permset_wrong_arg(self):
662         acl = ACL()
663         e = acl.append()
664         with pytest.raises(TypeError):
665           e.permset = object()
666
667     def test_permset_creation(self):
668         acl = ACL()
669         e = acl.append()
670         p1 = e.permset
671         p2 = Permset(e)
672         #self.assertEqual(p1, p2)
673
674     def test_permset_creation_wrong_arg(self):
675         with pytest.raises(TypeError):
676           Permset(object())
677
678     def test_permset(self):
679         """Test permissions"""
680         acl = posix1e.ACL()
681         e = acl.append()
682         ps = e.permset
683         ps.clear()
684         str_ps = str(ps)
685         self.checkRef(str_ps)
686         for perm in PERMSETS:
687             str_ps = str(ps)
688             txt = PERMSETS[perm][0]
689             self.checkRef(str_ps)
690             assert not ps.test(perm), ("Empty permission set should not"
691                                        " have permission '%s'" % txt)
692             ps.add(perm)
693             assert ps.test(perm), ("Permission '%s' should exist"
694                                    " after addition" % txt)
695             str_ps = str(ps)
696             self.checkRef(str_ps)
697             ps.delete(perm)
698             assert not ps.test(perm), ("Permission '%s' should not exist"
699                                        " after deletion" % txt)
700
701     def test_permset_via_accessors(self):
702         """Test permissions"""
703         acl = posix1e.ACL()
704         e = acl.append()
705         ps = e.permset
706         ps.clear()
707         str_ps = str(ps)
708         self.checkRef(str_ps)
709         def getter(perm):
710             return PERMSETS[perm][1].__get__(ps)
711         def setter(parm, value):
712             return PERMSETS[perm][1].__set__(ps, value)
713         for perm in PERMSETS:
714             str_ps = str(ps)
715             self.checkRef(str_ps)
716             txt = PERMSETS[perm][0]
717             assert not getter(perm), ("Empty permission set should not"
718                                       " have permission '%s'" % txt)
719             setter(perm, True)
720             assert ps.test(perm), ("Permission '%s' should exist"
721                                    " after addition" % txt)
722             assert getter(perm), ("Permission '%s' should exist"
723                                   " after addition" % txt)
724             str_ps = str(ps)
725             self.checkRef(str_ps)
726             setter(perm, False)
727             assert not ps.test(perm), ("Permission '%s' should not exist"
728                                        " after deletion" % txt)
729             assert not getter(perm), ("Permission '%s' should not exist"
730                                       " after deletion" % txt)
731
732     def test_permset_invalid_type(self):
733         acl = posix1e.ACL()
734         e = acl.append()
735         ps = e.permset
736         ps.clear()
737         with pytest.raises(TypeError):
738           ps.add("foobar")
739         with pytest.raises(TypeError):
740           ps.delete("foobar")
741         with pytest.raises(TypeError):
742           ps.test("foobar")
743         with pytest.raises(ValueError):
744           ps.write = object()
745
746     def test_qualifier_values(self):
747         """Tests qualifier correct store/retrieval"""
748         acl = posix1e.ACL()
749         e = acl.append()
750         # work around deprecation warnings
751         for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
752             qualifier = 1
753             e.tag_type = tag
754             while True:
755                 if tag == posix1e.ACL_USER:
756                     regex = re.compile("user with uid %d" % qualifier)
757                 else:
758                     regex = re.compile("group with gid %d" % qualifier)
759                 try:
760                     e.qualifier = qualifier
761                 except OverflowError:
762                     # reached overflow condition, break
763                     break
764                 assert e.qualifier == qualifier
765                 assert regex.search(str(e)) is not None
766                 qualifier *= 2
767
768     def test_qualifier_overflow(self):
769         """Tests qualifier overflow handling"""
770         acl = posix1e.ACL()
771         e = acl.append()
772         qualifier = sys.maxsize * 2
773         for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
774             e.tag_type = tag
775             with pytest.raises(OverflowError):
776                 e.qualifier = qualifier
777
778     def test_negative_qualifier(self):
779         """Tests negative qualifier handling"""
780         # Note: this presumes that uid_t/gid_t in C are unsigned...
781         acl = posix1e.ACL()
782         e = acl.append()
783         for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
784             e.tag_type = tag
785             for qualifier in [-10, -5, -1]:
786                 with pytest.raises(OverflowError):
787                     e.qualifier = qualifier
788
789     def test_invalid_qualifier(self):
790         """Tests invalid qualifier handling"""
791         acl = posix1e.ACL()
792         e = acl.append()
793         with pytest.raises(TypeError):
794           e.qualifier = object()
795         with pytest.raises((TypeError, AttributeError)):
796           del e.qualifier
797
798     def test_qualifier_on_wrong_tag(self):
799         """Tests qualifier setting on wrong tag"""
800         acl = posix1e.ACL()
801         e = acl.append()
802         e.tag_type = posix1e.ACL_OTHER
803         with pytest.raises(TypeError):
804           e.qualifier = 1
805         with pytest.raises(TypeError):
806           e.qualifier
807
808     @pytest.mark.parametrize("tag", ALL_TAG_VALUES, ids=ALL_TAG_DESCS)
809     def test_tag_types(self, tag):
810         """Tests tag type correct set/get"""
811         acl = posix1e.ACL()
812         e = acl.append()
813         e.tag_type = tag
814         assert e.tag_type == tag
815         # check we can show all tag types without breaking
816         assert str(e)
817
818     @pytest.mark.parametrize("src_tag", ALL_TAG_VALUES, ids=ALL_TAG_DESCS)
819     @pytest.mark.parametrize("dst_tag", ALL_TAG_VALUES, ids=ALL_TAG_DESCS)
820     def test_tag_overwrite(self, src_tag, dst_tag):
821         """Tests tag type correct set/get"""
822         acl = posix1e.ACL()
823         e = acl.append()
824         e.tag_type = src_tag
825         assert e.tag_type == src_tag
826         assert str(e)
827         e.tag_type = dst_tag
828         assert e.tag_type == dst_tag
829         assert str(e)
830
831     def test_invalid_tags(self):
832         """Tests tag type incorrect set/get"""
833         acl = posix1e.ACL()
834         e = acl.append()
835         with pytest.raises(TypeError):
836           e.tag_type = object()
837         e.tag_type = posix1e.ACL_USER_OBJ
838         # For some reason, PyPy raises AttributeError. Strange...
839         with pytest.raises((TypeError, AttributeError)):
840           del e.tag_type
841
842     def test_tag_wrong_overwrite(self):
843         acl = posix1e.ACL()
844         e = acl.append()
845         e.tag_type = posix1e.ACL_USER_OBJ
846         tag = max(ALL_TAG_VALUES) + 1
847         with pytest.raises(EnvironmentError):
848           e.tag_type = tag
849         # Check tag is still valid.
850         assert e.tag_type == posix1e.ACL_USER_OBJ
851
852 if __name__ == "__main__":
853     unittest.main()