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