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