]> git.k1024.org Git - pylibacl.git/blob - tests/test_acls.py
Restore setstate/getstate support
[pylibacl.git] / tests / test_acls.py
1 #
2 #
3
4 """Unittests for the posix1e module"""
5
6 #  Copyright (C) 2002-2009, 2012, 2014, 2015 Iustin Pop <iustin@k1024.org>
7 #
8 #  This library is free software; you can redistribute it and/or
9 #  modify it under the terms of the GNU Lesser General Public
10 #  License as published by the Free Software Foundation; either
11 #  version 2.1 of the License, or (at your option) any later version.
12 #
13 #  This library is distributed in the hope that it will be useful,
14 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 #  Lesser General Public License for more details.
17 #
18 #  You should have received a copy of the GNU Lesser General Public
19 #  License along with this library; if not, write to the Free Software
20 #  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 #  02110-1301  USA
22
23
24 import unittest
25 import os
26 import tempfile
27 import sys
28 import platform
29 import re
30 import errno
31 import operator
32 import pytest
33 import contextlib
34 import pathlib
35 import io
36
37 import posix1e
38 from posix1e import *
39
40 TEST_DIR = os.environ.get("TEST_DIR", ".")
41
42 BASIC_ACL_TEXT = "u::rw,g::r,o::-"
43
44 # Permset permission information
45 PERMSETS = {
46   posix1e.ACL_READ: ("read", posix1e.Permset.read),
47   posix1e.ACL_WRITE: ("write", posix1e.Permset.write),
48   posix1e.ACL_EXECUTE: ("execute", posix1e.Permset.execute),
49   }
50
51 ALL_TAGS = [
52   (posix1e.ACL_USER, "user"),
53   (posix1e.ACL_GROUP, "group"),
54   (posix1e.ACL_USER_OBJ, "user object"),
55   (posix1e.ACL_GROUP_OBJ, "group object"),
56   (posix1e.ACL_MASK, "mask"),
57   (posix1e.ACL_OTHER, "other"),
58 ]
59
60 ALL_TAG_VALUES = [i[0] for i in ALL_TAGS]
61 ALL_TAG_DESCS = [i[1] for i in ALL_TAGS]
62
63 # Fixtures and helpers
64
65 def ignore_ioerror(errnum, fn, *args, **kwargs):
66     """Call a function while ignoring some IOErrors.
67
68     This is needed as some OSes (e.g. FreeBSD) return failure (EINVAL)
69     when doing certain operations on an invalid ACL.
70
71     """
72     try:
73         fn(*args, **kwargs)
74     except IOError as err:
75         if err.errno == errnum:
76             return
77         raise
78
79 @pytest.fixture
80 def testdir():
81     """per-test temp dir based in TEST_DIR"""
82     with tempfile.TemporaryDirectory(dir=TEST_DIR) as dname:
83         yield dname
84
85 def get_file(path):
86     fh, fname = tempfile.mkstemp(".test", "xattr-", path)
87     return fh, fname
88
89 @contextlib.contextmanager
90 def get_file_name(path):
91     fh, fname = get_file(path)
92     os.close(fh)
93     yield fname
94
95 @contextlib.contextmanager
96 def get_file_fd(path):
97     fd = get_file(path)[0]
98     yield fd
99     os.close(fd)
100
101 @contextlib.contextmanager
102 def get_file_object(path):
103     fd = get_file(path)[0]
104     with os.fdopen(fd) as f:
105         yield f
106
107 @contextlib.contextmanager
108 def get_dir(path):
109     yield tempfile.mkdtemp(".test", "xattr-", path)
110
111 def get_symlink(path, dangling=True):
112     """create a symlink"""
113     fh, fname = get_file(path)
114     os.close(fh)
115     if dangling:
116         os.unlink(fname)
117     sname = fname + ".symlink"
118     os.symlink(fname, sname)
119     return fname, sname
120
121 @contextlib.contextmanager
122 def get_valid_symlink(path):
123     yield get_symlink(path, dangling=False)[1]
124
125 @contextlib.contextmanager
126 def get_dangling_symlink(path):
127     yield get_symlink(path, dangling=True)[1]
128
129 @contextlib.contextmanager
130 def get_file_and_symlink(path):
131     yield get_symlink(path, dangling=False)
132
133 @contextlib.contextmanager
134 def get_file_and_fobject(path):
135     fh, fname = get_file(path)
136     with os.fdopen(fh) as fo:
137         yield fname, fo
138
139 # Wrappers that build upon existing values
140
141 def as_wrapper(call, fn, closer=None):
142     @contextlib.contextmanager
143     def f(path):
144         with call(path) as r:
145             val = fn(r)
146             yield val
147             if closer is not None:
148                 closer(val)
149     return f
150
151 def as_bytes(call):
152     return as_wrapper(call, lambda r: r.encode())
153
154 def as_fspath(call):
155     return as_wrapper(call, pathlib.PurePath)
156
157 def as_iostream(call):
158     opener = lambda f: io.open(f, "r")
159     closer = lambda r: r.close()
160     return as_wrapper(call, opener, closer)
161
162 NOT_BEFORE_36 = pytest.mark.xfail(condition="sys.version_info < (3,6)",
163                                   strict=True)
164 NOT_PYPY = pytest.mark.xfail(condition="platform.python_implementation() == 'PyPy'",
165                                   strict=False)
166
167 require_acl_from_mode = pytest.mark.skipif("not HAS_ACL_FROM_MODE")
168 require_acl_check = pytest.mark.skipif("not HAS_ACL_CHECK")
169 require_acl_entry = pytest.mark.skipif("not HAS_ACL_ENTRY")
170 require_extended_check = pytest.mark.skipif("not HAS_EXTENDED_CHECK")
171 require_equiv_mode = pytest.mark.skipif("not HAS_EQUIV_MODE")
172 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         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     @require_copy_ext
440     def test_acl_copy_ext(self):
441         a = posix1e.ACL(text=BASIC_ACL_TEXT)
442         b = posix1e.ACL()
443         c = posix1e.ACL(acl=b)
444         assert a != b
445         assert b == c
446         state = a.__getstate__()
447         b.__setstate__(state)
448         assert a == b
449         assert b != c
450
451
452 class TestWrite:
453     """Write tests"""
454
455     def test_delete_default(self, testdir):
456         """Test removing the default ACL"""
457         with get_dir(testdir) as dname:
458           posix1e.delete_default(dname)
459
460     def test_delete_default_fail(self, testdir):
461         """Test removing the default ACL"""
462         with get_file_name(testdir) as fname:
463             with pytest.raises(IOError, match="no-such-file"):
464                 posix1e.delete_default(fname+".no-such-file")
465
466     @NOT_PYPY
467     def test_delete_default_wrong_arg(self):
468         with pytest.raises(TypeError):
469           posix1e.delete_default(object())
470
471     def test_reapply(self, testdir):
472         """Test re-applying an ACL"""
473         fd, fname = get_file(testdir)
474         acl1 = posix1e.ACL(fd=fd)
475         acl1.applyto(fd)
476         acl1.applyto(fname)
477         with get_dir(testdir) as dname:
478           acl2 = posix1e.ACL(file=fname)
479           acl2.applyto(dname)
480
481
482
483 @require_acl_entry
484 class TestModification:
485     """ACL modification tests"""
486
487     def checkRef(self, obj):
488         """Checks if a given obj has a 'sane' refcount"""
489         if platform.python_implementation() == "PyPy":
490             return
491         ref_cnt = sys.getrefcount(obj)
492         # FIXME: hardcoded value for the max ref count... but I've
493         # seen it overflow on bad reference counting, so it's better
494         # to be safe
495         if ref_cnt < 2 or ref_cnt > 1024:
496             pytest.fail("Wrong reference count, expected 2-1024 and got %d" %
497                         ref_cnt)
498
499     def test_str(self):
500         """Test str() of an ACL."""
501         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
502         str_acl = str(acl)
503         self.checkRef(str_acl)
504
505     def test_append(self):
506         """Test append a new Entry to the ACL"""
507         acl = posix1e.ACL()
508         e = acl.append()
509         e.tag_type = posix1e.ACL_OTHER
510         ignore_ioerror(errno.EINVAL, acl.calc_mask)
511         str_format = str(e)
512         self.checkRef(str_format)
513         e2 = acl.append(e)
514         ignore_ioerror(errno.EINVAL, acl.calc_mask)
515         assert not acl.valid()
516
517     def test_wrong_append(self):
518         """Test append a new Entry to the ACL based on wrong object type"""
519         acl = posix1e.ACL()
520         with pytest.raises(TypeError):
521           acl.append(object())
522
523     def test_entry_creation(self):
524         acl = posix1e.ACL()
525         e = posix1e.Entry(acl)
526         ignore_ioerror(errno.EINVAL, acl.calc_mask)
527         str_format = str(e)
528         self.checkRef(str_format)
529
530     def test_entry_failed_creation(self):
531         # Checks for partial initialisation and deletion on error
532         # path.
533         with pytest.raises(TypeError):
534           posix1e.Entry(object())
535
536     def test_entry_reinitialisations(self):
537         a = posix1e.ACL()
538         b = posix1e.ACL()
539         e = posix1e.Entry(a)
540         e.__init__(a)
541         with pytest.raises(ValueError, match="different parent"):
542             e.__init__(b)
543
544     @NOT_PYPY
545     def test_entry_reinit_leaks_refcount(self):
546         acl = posix1e.ACL()
547         e = acl.append()
548         ref = sys.getrefcount(acl)
549         e.__init__(acl)
550         assert ref == sys.getrefcount(acl), "Uh-oh, ref leaks..."
551
552     def test_delete(self):
553         """Test delete Entry from the ACL"""
554         acl = posix1e.ACL()
555         e = acl.append()
556         e.tag_type = posix1e.ACL_OTHER
557         ignore_ioerror(errno.EINVAL, acl.calc_mask)
558         acl.delete_entry(e)
559         ignore_ioerror(errno.EINVAL, acl.calc_mask)
560
561     def test_double_delete(self):
562         """Test delete Entry from the ACL"""
563         # This is not entirely valid/correct, since the entry object
564         # itself is invalid after the first deletion, so we're
565         # actually testing deleting an invalid object, not a
566         # non-existing entry...
567         acl = posix1e.ACL()
568         e = acl.append()
569         e.tag_type = posix1e.ACL_OTHER
570         ignore_ioerror(errno.EINVAL, acl.calc_mask)
571         acl.delete_entry(e)
572         ignore_ioerror(errno.EINVAL, acl.calc_mask)
573         with pytest.raises(EnvironmentError):
574           acl.delete_entry(e)
575
576     # This currently fails as this deletion seems to be accepted :/
577     @pytest.mark.xfail(reason="Entry deletion is unreliable")
578     def testDeleteInvalidEntry(self):
579         """Test delete foreign Entry from the ACL"""
580         acl1 = posix1e.ACL()
581         acl2 = posix1e.ACL()
582         e = acl1.append()
583         e.tag_type = posix1e.ACL_OTHER
584         ignore_ioerror(errno.EINVAL, acl1.calc_mask)
585         with pytest.raises(EnvironmentError):
586           acl2.delete_entry(e)
587
588     def test_delete_invalid_object(self):
589         """Test delete a non-Entry from the ACL"""
590         acl = posix1e.ACL()
591         with pytest.raises(TypeError):
592           acl.delete_entry(object())
593
594     def test_double_entries(self):
595         """Test double entries"""
596         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
597         assert acl.valid()
598         for tag_type in (posix1e.ACL_USER_OBJ, posix1e.ACL_GROUP_OBJ,
599                          posix1e.ACL_OTHER):
600             e = acl.append()
601             e.tag_type = tag_type
602             e.permset.clear()
603             assert not acl.valid(), ("ACL containing duplicate entries"
604                                      " should not be valid")
605             acl.delete_entry(e)
606
607     def test_multiple_good_entries(self):
608         """Test multiple valid entries"""
609         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
610         assert acl.valid()
611         for tag_type in (posix1e.ACL_USER,
612                          posix1e.ACL_GROUP):
613             for obj_id in range(5):
614                 e = acl.append()
615                 e.tag_type = tag_type
616                 e.qualifier = obj_id
617                 e.permset.clear()
618                 acl.calc_mask()
619                 assert acl.valid(), ("ACL should be able to hold multiple"
620                                      " user/group entries")
621
622     def test_multiple_bad_entries(self):
623         """Test multiple invalid entries"""
624         for tag_type in (posix1e.ACL_USER,
625                          posix1e.ACL_GROUP):
626             acl = posix1e.ACL(text=BASIC_ACL_TEXT)
627             assert acl.valid()
628             e1 = acl.append()
629             e1.tag_type = tag_type
630             e1.qualifier = 0
631             e1.permset.clear()
632             acl.calc_mask()
633             assert acl.valid(), ("ACL should be able to add a"
634                                  " user/group entry")
635             e2 = acl.append()
636             e2.tag_type = tag_type
637             e2.qualifier = 0
638             e2.permset.clear()
639             ignore_ioerror(errno.EINVAL, acl.calc_mask)
640             assert not acl.valid(), ("ACL should not validate when"
641                                      " containing two duplicate entries")
642             acl.delete_entry(e1)
643             # FreeBSD trips over itself here and can't delete the
644             # entry, even though it still exists.
645             ignore_ioerror(errno.EINVAL, acl.delete_entry, e2)
646
647     def test_copy(self):
648         acl = ACL()
649         e1 = acl.append()
650         e1.tag_type = ACL_USER
651         p1 = e1.permset
652         p1.clear()
653         p1.read = True
654         p1.write = True
655         e2 = acl.append()
656         e2.tag_type = ACL_GROUP
657         p2 = e2.permset
658         p2.clear()
659         p2.read = True
660         assert not p2.write
661         e2.copy(e1)
662         assert p2.write
663         assert e1.tag_type == e2.tag_type
664
665     def test_copy_wrong_arg(self):
666         acl = ACL()
667         e = acl.append()
668         with pytest.raises(TypeError):
669           e.copy(object())
670
671     def test_set_permset(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.permset = p1
686         assert e2.permset.write
687         assert e2.tag_type == ACL_GROUP
688
689     def test_set_permset_wrong_arg(self):
690         acl = ACL()
691         e = acl.append()
692         with pytest.raises(TypeError):
693           e.permset = object()
694
695     def test_permset_creation(self):
696         acl = ACL()
697         e = acl.append()
698         p1 = e.permset
699         p2 = Permset(e)
700         #self.assertEqual(p1, p2)
701
702     def test_permset_creation_wrong_arg(self):
703         with pytest.raises(TypeError):
704           Permset(object())
705
706     def test_permset(self):
707         """Test permissions"""
708         acl = posix1e.ACL()
709         e = acl.append()
710         ps = e.permset
711         ps.clear()
712         str_ps = str(ps)
713         self.checkRef(str_ps)
714         for perm in PERMSETS:
715             str_ps = str(ps)
716             txt = PERMSETS[perm][0]
717             self.checkRef(str_ps)
718             assert not ps.test(perm), ("Empty permission set should not"
719                                        " have permission '%s'" % txt)
720             ps.add(perm)
721             assert ps.test(perm), ("Permission '%s' should exist"
722                                    " after addition" % txt)
723             str_ps = str(ps)
724             self.checkRef(str_ps)
725             ps.delete(perm)
726             assert not ps.test(perm), ("Permission '%s' should not exist"
727                                        " after deletion" % txt)
728
729     def test_permset_via_accessors(self):
730         """Test permissions"""
731         acl = posix1e.ACL()
732         e = acl.append()
733         ps = e.permset
734         ps.clear()
735         str_ps = str(ps)
736         self.checkRef(str_ps)
737         def getter(perm):
738             return PERMSETS[perm][1].__get__(ps)
739         def setter(parm, value):
740             return PERMSETS[perm][1].__set__(ps, value)
741         for perm in PERMSETS:
742             str_ps = str(ps)
743             self.checkRef(str_ps)
744             txt = PERMSETS[perm][0]
745             assert not getter(perm), ("Empty permission set should not"
746                                       " have permission '%s'" % txt)
747             setter(perm, True)
748             assert ps.test(perm), ("Permission '%s' should exist"
749                                    " after addition" % txt)
750             assert getter(perm), ("Permission '%s' should exist"
751                                   " after addition" % txt)
752             str_ps = str(ps)
753             self.checkRef(str_ps)
754             setter(perm, False)
755             assert not ps.test(perm), ("Permission '%s' should not exist"
756                                        " after deletion" % txt)
757             assert not getter(perm), ("Permission '%s' should not exist"
758                                       " after deletion" % txt)
759
760     def test_permset_invalid_type(self):
761         acl = posix1e.ACL()
762         e = acl.append()
763         ps = e.permset
764         ps.clear()
765         with pytest.raises(TypeError):
766           ps.add("foobar")
767         with pytest.raises(TypeError):
768           ps.delete("foobar")
769         with pytest.raises(TypeError):
770           ps.test("foobar")
771         with pytest.raises(ValueError):
772           ps.write = object()
773
774     def test_qualifier_values(self):
775         """Tests qualifier correct store/retrieval"""
776         acl = posix1e.ACL()
777         e = acl.append()
778         # work around deprecation warnings
779         for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
780             qualifier = 1
781             e.tag_type = tag
782             while True:
783                 if tag == posix1e.ACL_USER:
784                     regex = re.compile("user with uid %d" % qualifier)
785                 else:
786                     regex = re.compile("group with gid %d" % qualifier)
787                 try:
788                     e.qualifier = qualifier
789                 except OverflowError:
790                     # reached overflow condition, break
791                     break
792                 assert e.qualifier == qualifier
793                 assert regex.search(str(e)) is not None
794                 qualifier *= 2
795
796     def test_qualifier_overflow(self):
797         """Tests qualifier overflow handling"""
798         acl = posix1e.ACL()
799         e = acl.append()
800         qualifier = sys.maxsize * 2
801         for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
802             e.tag_type = tag
803             with pytest.raises(OverflowError):
804                 e.qualifier = qualifier
805
806     def test_negative_qualifier(self):
807         """Tests negative qualifier handling"""
808         # Note: this presumes that uid_t/gid_t in C are unsigned...
809         acl = posix1e.ACL()
810         e = acl.append()
811         for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
812             e.tag_type = tag
813             for qualifier in [-10, -5, -1]:
814                 with pytest.raises(OverflowError):
815                     e.qualifier = qualifier
816
817     def test_invalid_qualifier(self):
818         """Tests invalid qualifier handling"""
819         acl = posix1e.ACL()
820         e = acl.append()
821         with pytest.raises(TypeError):
822           e.qualifier = object()
823         with pytest.raises((TypeError, AttributeError)):
824           del e.qualifier
825
826     def test_qualifier_on_wrong_tag(self):
827         """Tests qualifier setting on wrong tag"""
828         acl = posix1e.ACL()
829         e = acl.append()
830         e.tag_type = posix1e.ACL_OTHER
831         with pytest.raises(TypeError):
832           e.qualifier = 1
833         with pytest.raises(TypeError):
834           e.qualifier
835
836     @pytest.mark.parametrize("tag", ALL_TAG_VALUES, ids=ALL_TAG_DESCS)
837     def test_tag_types(self, tag):
838         """Tests tag type correct set/get"""
839         acl = posix1e.ACL()
840         e = acl.append()
841         e.tag_type = tag
842         assert e.tag_type == tag
843         # check we can show all tag types without breaking
844         assert str(e)
845
846     @pytest.mark.parametrize("src_tag", ALL_TAG_VALUES, ids=ALL_TAG_DESCS)
847     @pytest.mark.parametrize("dst_tag", ALL_TAG_VALUES, ids=ALL_TAG_DESCS)
848     def test_tag_overwrite(self, src_tag, dst_tag):
849         """Tests tag type correct set/get"""
850         acl = posix1e.ACL()
851         e = acl.append()
852         e.tag_type = src_tag
853         assert e.tag_type == src_tag
854         assert str(e)
855         e.tag_type = dst_tag
856         assert e.tag_type == dst_tag
857         assert str(e)
858
859     def test_invalid_tags(self):
860         """Tests tag type incorrect set/get"""
861         acl = posix1e.ACL()
862         e = acl.append()
863         with pytest.raises(TypeError):
864           e.tag_type = object()
865         e.tag_type = posix1e.ACL_USER_OBJ
866         # For some reason, PyPy raises AttributeError. Strange...
867         with pytest.raises((TypeError, AttributeError)):
868           del e.tag_type
869
870     def test_tag_wrong_overwrite(self):
871         acl = posix1e.ACL()
872         e = acl.append()
873         e.tag_type = posix1e.ACL_USER_OBJ
874         tag = max(ALL_TAG_VALUES) + 1
875         with pytest.raises(EnvironmentError):
876           e.tag_type = tag
877         # Check tag is still valid.
878         assert e.tag_type == posix1e.ACL_USER_OBJ
879
880 if __name__ == "__main__":
881     unittest.main()