]> git.k1024.org Git - pylibacl.git/blob - tests/test_acls.py
Convert AclExtensions to pytest
[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
35 import posix1e
36 from posix1e import *
37
38 try:
39   import __pypy__
40 except ImportError:
41   __pypy__ = None
42
43 TEST_DIR = os.environ.get("TEST_DIR", ".")
44
45 BASIC_ACL_TEXT = "u::rw,g::r,o::-"
46
47 # This is to workaround python 2/3 differences at syntactic level
48 # (which can't be worked around via if's)
49 M0500 = 320 # octal 0500
50 M0644 = 420 # octal 0644
51 M0755 = 493 # octal 755
52
53 # Permset permission information
54 PERMSETS = {
55   posix1e.ACL_READ: ("read", posix1e.Permset.read),
56   posix1e.ACL_WRITE: ("write", posix1e.Permset.write),
57   posix1e.ACL_EXECUTE: ("execute", posix1e.Permset.execute),
58   }
59
60
61 # Check if running under Python 3
62 IS_PY_3K = sys.hexversion >= 0x03000000
63
64 # Fixtures and helpers
65
66 def ignore_ioerror(errnum, fn, *args, **kwargs):
67     """Call a function while ignoring some IOErrors.
68
69     This is needed as some OSes (e.g. FreeBSD) return failure (EINVAL)
70     when doing certain operations on an invalid ACL.
71
72     """
73     try:
74         fn(*args, **kwargs)
75     except IOError:
76         err = sys.exc_info()[1]
77         if err.errno == errnum:
78             return
79         raise
80
81 def encode(s):
82     """Encode a string if needed (under Python 3)"""
83     if IS_PY_3K:
84         return s.encode()
85     else:
86         return s
87
88 @pytest.fixture
89 def testdir():
90     """per-test temp dir based in TEST_DIR"""
91     with tempfile.TemporaryDirectory(dir=TEST_DIR) as dname:
92         yield dname
93
94 def get_file(path):
95     fh, fname = tempfile.mkstemp(".test", "xattr-", path)
96     return fh, fname
97
98 @contextlib.contextmanager
99 def get_file_name(path):
100     fh, fname = get_file(path)
101     os.close(fh)
102     yield fname
103
104 @contextlib.contextmanager
105 def get_file_fd(path):
106     fd = get_file(path)[0]
107     yield fd
108     os.close(fd)
109
110 @contextlib.contextmanager
111 def get_file_object(path):
112     fd = get_file(path)[0]
113     with os.fdopen(fd) as f:
114         yield f
115
116 @contextlib.contextmanager
117 def get_dir(path):
118     yield tempfile.mkdtemp(".test", "xattr-", path)
119
120 def get_symlink(path, dangling=True):
121     """create a symlink"""
122     fh, fname = get_file(path)
123     os.close(fh)
124     if dangling:
125         os.unlink(fname)
126     sname = fname + ".symlink"
127     os.symlink(fname, sname)
128     return fname, sname
129
130 @contextlib.contextmanager
131 def get_valid_symlink(path):
132     yield get_symlink(path, dangling=False)[1]
133
134 @contextlib.contextmanager
135 def get_dangling_symlink(path):
136     yield get_symlink(path, dangling=True)[1]
137
138 @contextlib.contextmanager
139 def get_file_and_symlink(path):
140     yield get_symlink(path, dangling=False)
141
142 @contextlib.contextmanager
143 def get_file_and_fobject(path):
144     fh, fname = get_file(path)
145     with os.fdopen(fh) as fo:
146         yield fname, fo
147
148 # Wrappers that build upon existing values
149
150 def as_wrapper(call, fn, closer=None):
151     @contextlib.contextmanager
152     def f(path):
153         with call(path) as r:
154             val = fn(r)
155             yield val
156             if closer is not None:
157                 closer(val)
158     return f
159
160 def as_bytes(call):
161     return as_wrapper(call, lambda r: r.encode())
162
163 def as_fspath(call):
164     return as_wrapper(call, pathlib.PurePath)
165
166 def as_iostream(call):
167     opener = lambda f: io.open(f, "r")
168     closer = lambda r: r.close()
169     return as_wrapper(call, opener, closer)
170
171 NOT_BEFORE_36 = pytest.mark.xfail(condition="sys.version_info < (3,6)",
172                                   strict=True)
173 NOT_PYPY = pytest.mark.xfail(condition="platform.python_implementation() == 'PyPy'",
174                                   strict=False)
175
176 require_acl_from_mode = pytest.mark.skipif("not HAS_ACL_FROM_MODE")
177 require_acl_check = pytest.mark.skipif("not HAS_ACL_CHECK")
178 require_acl_entry = pytest.mark.skipif("not HAS_ACL_ENTRY")
179 require_extended_check = pytest.mark.skipif("not HAS_EXTENDED_CHECK")
180 require_equiv_mode = pytest.mark.skipif("not HAS_EQUIV_MODE")
181
182 class aclTest:
183     """Support functions ACLs"""
184
185     def setUp(self):
186         """set up function"""
187         self.rmfiles = []
188         self.rmdirs = []
189
190     def tearDown(self):
191         """tear down function"""
192         for fname in self.rmfiles:
193             os.unlink(fname)
194         for dname in self.rmdirs:
195             os.rmdir(dname)
196
197     def _getfile(self):
198         """create a temp file"""
199         fh, fname = tempfile.mkstemp(".test", "xattr-", TEST_DIR)
200         self.rmfiles.append(fname)
201         return fh, fname
202
203     def _getdir(self):
204         """create a temp dir"""
205         dname = tempfile.mkdtemp(".test", "xattr-", TEST_DIR)
206         self.rmdirs.append(dname)
207         return dname
208
209     def _getsymlink(self):
210         """create a symlink"""
211         fh, fname = self._getfile()
212         os.close(fh)
213         os.unlink(fname)
214         os.symlink(fname + ".non-existent", fname)
215         return fname
216
217
218 class TestLoad:
219     """Load/create tests"""
220     def test_from_file(self, testdir):
221         """Test loading ACLs from a file"""
222         _, fname = get_file(testdir)
223         acl1 = posix1e.ACL(file=fname)
224         assert acl1.valid()
225
226     def test_from_dir(self, testdir):
227         """Test loading ACLs from a directory"""
228         with get_dir(testdir) as dname:
229           acl1 = posix1e.ACL(file=dname)
230           acl2 = posix1e.ACL(filedef=dname)
231           assert acl1.valid()
232         # default ACLs might or might not be valid; missing ones are
233         # not valid, so we don't test acl2 for validity
234
235     def test_from_fd(self, testdir):
236         """Test loading ACLs from a file descriptor"""
237         fd, _ = get_file(testdir)
238         acl1 = posix1e.ACL(fd=fd)
239         assert acl1.valid()
240
241     def test_from_empty_invalid(self):
242         """Test creating an empty ACL"""
243         acl1 = posix1e.ACL()
244         assert not acl1.valid()
245
246     def test_from_text(self):
247         """Test creating an ACL from text"""
248         acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
249         assert acl1.valid()
250
251     def test_from_acl(self):
252         """Test creating an ACL from an existing ACL"""
253         acl1 = posix1e.ACL()
254         acl2 = posix1e.ACL(acl=acl1)
255         assert acl1 == acl2
256
257     def test_invalid_creation_params(self, testdir):
258         """Test that creating an ACL from multiple objects fails"""
259         fd, _ = get_file(testdir)
260         with pytest.raises(ValueError):
261           posix1e.ACL(text=BASIC_ACL_TEXT, fd=fd)
262
263     def test_invalid_value_creation(self):
264         """Test that creating an ACL from wrong specification fails"""
265         with pytest.raises(EnvironmentError):
266           posix1e.ACL(text="foobar")
267         with pytest.raises(TypeError):
268           posix1e.ACL(foo="bar")
269
270     def test_double_init(self):
271         acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
272         assert acl1.valid()
273         acl1.__init__(text=BASIC_ACL_TEXT)
274         assert acl1.valid()
275
276 class TestAclExtensions:
277     """ACL extensions checks"""
278
279     @require_acl_from_mode
280     def test_from_mode(self):
281         """Test loading ACLs from an octal mode"""
282         acl1 = posix1e.ACL(mode=M0644)
283         assert acl1.valid()
284
285     @require_acl_check
286     def test_acl_check(self):
287         """Test the acl_check method"""
288         acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
289         assert not acl1.check()
290         acl2 = posix1e.ACL()
291         assert acl2.check()
292
293     @require_extended_check
294     def test_extended(self, testdir):
295         """Test the acl_extended function"""
296         fd, fname = get_file(testdir)
297         basic_acl = posix1e.ACL(text=BASIC_ACL_TEXT)
298         basic_acl.applyto(fd)
299         for item in fd, fname:
300             assert not has_extended(item)
301         enhanced_acl = posix1e.ACL(text="u::rw,g::-,o::-,u:root:rw,mask::r")
302         assert enhanced_acl.valid()
303         enhanced_acl.applyto(fd)
304         for item in fd, fname:
305             assert has_extended(item)
306
307     @require_extended_check
308     def test_extended_arg_handling(self):
309       with pytest.raises(TypeError):
310         has_extended()
311       with pytest.raises(TypeError):
312         has_extended(object())
313
314     @require_equiv_mode
315     def test_equiv_mode(self):
316         """Test the equiv_mode function"""
317         if HAS_ACL_FROM_MODE:
318             for mode in M0644, M0755:
319                 acl = posix1e.ACL(mode=mode)
320                 assert acl.equiv_mode() == mode
321         acl = posix1e.ACL(text="u::rw,g::r,o::r")
322         assert acl.equiv_mode() == 0o644
323         acl = posix1e.ACL(text="u::rx,g::-,o::-")
324         assert acl.equiv_mode() == 0o500
325
326     @require_acl_check
327     def test_to_any_text(self):
328         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
329         assert encode("u::") in \
330           acl.to_any_text(options=posix1e.TEXT_ABBREVIATE)
331         assert encode("user::") in acl.to_any_text()
332
333     @require_acl_check
334     def test_to_any_text_wrong_args(self):
335         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
336         with pytest.raises(TypeError):
337           acl.to_any_text(foo="bar")
338
339
340     @require_acl_check
341     def test_rich_compare(self):
342         acl1 = posix1e.ACL(text="u::rw,g::r,o::r")
343         acl2 = posix1e.ACL(acl=acl1)
344         acl3 = posix1e.ACL(text="u::rw,g::rw,o::r")
345         assert acl1 == acl2
346         assert acl1 != acl3
347         with pytest.raises(TypeError):
348           acl1 < acl2
349         with pytest.raises(TypeError):
350           acl1 >= acl3
351         assert acl1 != True
352         assert not (acl1 == 1)
353         with pytest.raises(TypeError):
354           acl1 > True
355
356     @pytest.mark.skipif(not hasattr(posix1e.ACL, "__cmp__"), reason="__cmp__ is missing")
357     @pytest.mark.skipif(__pypy__ is not None, reason="Disabled under pypy")
358     def test_cmp(self):
359         acl1 = posix1e.ACL()
360         with pytest.raises(TypeError):
361           acl1.__cmp__(acl1)
362
363     def test_apply_to_with_wrong_object(self):
364         acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
365         assert acl1.valid()
366         with pytest.raises(TypeError):
367           acl1.applyto(object())
368         with pytest.raises(TypeError):
369           acl1.applyto(object(), object())
370
371     @require_acl_entry
372     def test_acl_iterator(self):
373         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
374         for entry in acl:
375             assert entry.parent is acl
376
377
378 class TestWrite:
379     """Write tests"""
380
381     def test_delete_default(self, testdir):
382         """Test removing the default ACL"""
383         with get_dir(testdir) as dname:
384           posix1e.delete_default(dname)
385
386     @pytest.mark.skipif(__pypy__, reason="Disabled under pypy")
387     def test_delete_default_wrong_arg(self):
388         with pytest.raises(TypeError):
389           posix1e.delete_default(object())
390
391     def test_reapply(self, testdir):
392         """Test re-applying an ACL"""
393         fd, fname = get_file(testdir)
394         acl1 = posix1e.ACL(fd=fd)
395         acl1.applyto(fd)
396         acl1.applyto(fname)
397         with get_dir(testdir) as dname:
398           acl2 = posix1e.ACL(file=fname)
399           acl2.applyto(dname)
400
401
402 @unittest.skipUnless(HAS_ACL_ENTRY, "ACL entries not supported")
403 class ModificationTests(aclTest, unittest.TestCase):
404     """ACL modification tests"""
405
406     def checkRef(self, obj):
407         """Checks if a given obj has a 'sane' refcount"""
408         if platform.python_implementation() == "PyPy":
409             return
410         ref_cnt = sys.getrefcount(obj)
411         # FIXME: hardcoded value for the max ref count... but I've
412         # seen it overflow on bad reference counting, so it's better
413         # to be safe
414         if ref_cnt < 2 or ref_cnt > 1024:
415             self.fail("Wrong reference count, expected 2-1024 and got %d" %
416                       ref_cnt)
417
418     def testStr(self):
419         """Test str() of an ACL."""
420         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
421         str_acl = str(acl)
422         self.checkRef(str_acl)
423
424     def testAppend(self):
425         """Test append a new Entry to the ACL"""
426         acl = posix1e.ACL()
427         e = acl.append()
428         e.tag_type = posix1e.ACL_OTHER
429         ignore_ioerror(errno.EINVAL, acl.calc_mask)
430         str_format = str(e)
431         self.checkRef(str_format)
432         e2 = acl.append(e)
433         ignore_ioerror(errno.EINVAL, acl.calc_mask)
434         self.assertFalse(acl.valid())
435
436     def testWrongAppend(self):
437         """Test append a new Entry to the ACL based on wrong object type"""
438         acl = posix1e.ACL()
439         self.assertRaises(TypeError, acl.append, object())
440
441     def testEntryCreation(self):
442         acl = posix1e.ACL()
443         e = posix1e.Entry(acl)
444         ignore_ioerror(errno.EINVAL, acl.calc_mask)
445         str_format = str(e)
446         self.checkRef(str_format)
447
448     def testEntryFailedCreation(self):
449         # Checks for partial initialisation and deletion on error
450         # path.
451         self.assertRaises(TypeError, posix1e.Entry, object())
452
453     def testDelete(self):
454         """Test delete Entry from the ACL"""
455         acl = posix1e.ACL()
456         e = acl.append()
457         e.tag_type = posix1e.ACL_OTHER
458         ignore_ioerror(errno.EINVAL, acl.calc_mask)
459         acl.delete_entry(e)
460         ignore_ioerror(errno.EINVAL, acl.calc_mask)
461
462     def testDoubleDelete(self):
463         """Test delete Entry from the ACL"""
464         # This is not entirely valid/correct, since the entry object
465         # itself is invalid after the first deletion, so we're
466         # actually testing deleting an invalid object, not a
467         # non-existing entry...
468         acl = posix1e.ACL()
469         e = acl.append()
470         e.tag_type = posix1e.ACL_OTHER
471         ignore_ioerror(errno.EINVAL, acl.calc_mask)
472         acl.delete_entry(e)
473         ignore_ioerror(errno.EINVAL, acl.calc_mask)
474         self.assertRaises(EnvironmentError, acl.delete_entry, e)
475
476     # This currently fails as this deletion seems to be accepted :/
477     @unittest.skip("Entry deletion is unreliable")
478     def testDeleteInvalidEntry(self):
479         """Test delete foreign Entry from the ACL"""
480         acl1 = posix1e.ACL()
481         acl2 = posix1e.ACL()
482         e = acl1.append()
483         e.tag_type = posix1e.ACL_OTHER
484         ignore_ioerror(errno.EINVAL, acl1.calc_mask)
485         self.assertRaises(EnvironmentError, acl2.delete_entry, e)
486
487     def testDeleteInvalidObject(self):
488         """Test delete a non-Entry from the ACL"""
489         acl = posix1e.ACL()
490         self.assertRaises(TypeError, acl.delete_entry, object())
491
492     def testDoubleEntries(self):
493         """Test double entries"""
494         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
495         self.assertTrue(acl.valid(), "ACL is not valid")
496         for tag_type in (posix1e.ACL_USER_OBJ, posix1e.ACL_GROUP_OBJ,
497                          posix1e.ACL_OTHER):
498             e = acl.append()
499             e.tag_type = tag_type
500             e.permset.clear()
501             self.assertFalse(acl.valid(),
502                 "ACL containing duplicate entries"
503                 " should not be valid")
504             acl.delete_entry(e)
505
506     def testMultipleGoodEntries(self):
507         """Test multiple valid entries"""
508         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
509         self.assertTrue(acl.valid(), "ACL is not valid")
510         for tag_type in (posix1e.ACL_USER,
511                          posix1e.ACL_GROUP):
512             for obj_id in range(5):
513                 e = acl.append()
514                 e.tag_type = tag_type
515                 e.qualifier = obj_id
516                 e.permset.clear()
517                 acl.calc_mask()
518                 self.assertTrue(acl.valid(),
519                     "ACL should be able to hold multiple"
520                     " user/group entries")
521
522     def testMultipleBadEntries(self):
523         """Test multiple invalid entries"""
524         for tag_type in (posix1e.ACL_USER,
525                          posix1e.ACL_GROUP):
526             acl = posix1e.ACL(text=BASIC_ACL_TEXT)
527             self.assertTrue(acl.valid(), "ACL built from standard description"
528                                          " should be valid")
529             e1 = acl.append()
530             e1.tag_type = tag_type
531             e1.qualifier = 0
532             e1.permset.clear()
533             acl.calc_mask()
534             self.assertTrue(acl.valid(), "ACL should be able to add a"
535                 " user/group entry")
536             e2 = acl.append()
537             e2.tag_type = tag_type
538             e2.qualifier = 0
539             e2.permset.clear()
540             ignore_ioerror(errno.EINVAL, acl.calc_mask)
541             self.assertFalse(acl.valid(), "ACL should not validate when"
542                 " containing two duplicate entries")
543             acl.delete_entry(e1)
544             # FreeBSD trips over itself here and can't delete the
545             # entry, even though it still exists.
546             ignore_ioerror(errno.EINVAL, acl.delete_entry, e2)
547
548     def testCopy(self):
549         acl = ACL()
550         e1 = acl.append()
551         e1.tag_type = ACL_USER
552         p1 = e1.permset
553         p1.clear()
554         p1.read = True
555         p1.write = True
556         e2 = acl.append()
557         e2.tag_type = ACL_GROUP
558         p2 = e2.permset
559         p2.clear()
560         p2.read = True
561         self.assertFalse(p2.write)
562         e2.copy(e1)
563         self.assertTrue(p2.write)
564         self.assertEqual(e1.tag_type, e2.tag_type)
565
566     def testCopyWrongArg(self):
567         acl = ACL()
568         e = acl.append()
569         self.assertRaises(TypeError, e.copy, object())
570
571     def testSetPermset(self):
572         acl = ACL()
573         e1 = acl.append()
574         e1.tag_type = ACL_USER
575         p1 = e1.permset
576         p1.clear()
577         p1.read = True
578         p1.write = True
579         e2 = acl.append()
580         e2.tag_type = ACL_GROUP
581         p2 = e2.permset
582         p2.clear()
583         p2.read = True
584         self.assertFalse(p2.write)
585         e2.permset = p1
586         self.assertTrue(e2.permset.write)
587         self.assertEqual(e2.tag_type, ACL_GROUP)
588
589     def testSetPermsetWrongArg(self):
590         acl = ACL()
591         e = acl.append()
592         def setter(v):
593             e.permset = v
594         self.assertRaises(TypeError, setter, object())
595
596     def testPermsetCreation(self):
597         acl = ACL()
598         e = acl.append()
599         p1 = e.permset
600         p2 = Permset(e)
601         #self.assertEqual(p1, p2)
602
603     def testPermsetCreationWrongArg(self):
604         self.assertRaises(TypeError, Permset, object())
605
606     def testPermset(self):
607         """Test permissions"""
608         acl = posix1e.ACL()
609         e = acl.append()
610         ps = e.permset
611         ps.clear()
612         str_ps = str(ps)
613         self.checkRef(str_ps)
614         for perm in PERMSETS:
615             str_ps = str(ps)
616             txt = PERMSETS[perm][0]
617             self.checkRef(str_ps)
618             self.assertFalse(ps.test(perm), "Empty permission set should not"
619                 " have permission '%s'" % txt)
620             ps.add(perm)
621             self.assertTrue(ps.test(perm), "Permission '%s' should exist"
622                 " after addition" % txt)
623             str_ps = str(ps)
624             self.checkRef(str_ps)
625             ps.delete(perm)
626             self.assertFalse(ps.test(perm), "Permission '%s' should not exist"
627                 " after deletion" % txt)
628
629     def testPermsetViaAccessors(self):
630         """Test permissions"""
631         acl = posix1e.ACL()
632         e = acl.append()
633         ps = e.permset
634         ps.clear()
635         str_ps = str(ps)
636         self.checkRef(str_ps)
637         def getter(perm):
638             return PERMSETS[perm][1].__get__(ps)
639         def setter(parm, value):
640             return PERMSETS[perm][1].__set__(ps, value)
641         for perm in PERMSETS:
642             str_ps = str(ps)
643             self.checkRef(str_ps)
644             txt = PERMSETS[perm][0]
645             self.assertFalse(getter(perm), "Empty permission set should not"
646                 " have permission '%s'" % txt)
647             setter(perm, True)
648             self.assertTrue(ps.test(perm), "Permission '%s' should exist"
649                 " after addition" % txt)
650             self.assertTrue(getter(perm), "Permission '%s' should exist"
651                 " after addition" % txt)
652             str_ps = str(ps)
653             self.checkRef(str_ps)
654             setter(perm, False)
655             self.assertFalse(ps.test(perm), "Permission '%s' should not exist"
656                 " after deletion" % txt)
657             self.assertFalse(getter(perm), "Permission '%s' should not exist"
658                 " after deletion" % txt)
659
660     def testPermsetInvalidType(self):
661         acl = posix1e.ACL()
662         e = acl.append()
663         ps = e.permset
664         ps.clear()
665         def setter():
666             ps.write = object()
667         self.assertRaises(TypeError, ps.add, "foobar")
668         self.assertRaises(TypeError, ps.delete, "foobar")
669         self.assertRaises(TypeError, ps.test, "foobar")
670         self.assertRaises(ValueError, setter)
671
672     @unittest.skipUnless(IS_PY_3K, "Only supported under Python 3")
673     def testQualifierValues(self):
674         """Tests qualifier correct store/retrieval"""
675         acl = posix1e.ACL()
676         e = acl.append()
677         # work around deprecation warnings
678         if hasattr(self, 'assertRegex'):
679             fn = self.assertRegex
680         else:
681             fn = self.assertRegexpMatches
682         for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
683             qualifier = 1
684             e.tag_type = tag
685             while True:
686                 if tag == posix1e.ACL_USER:
687                     regex = re.compile("user with uid %d" % qualifier)
688                 else:
689                     regex = re.compile("group with gid %d" % qualifier)
690                 try:
691                     e.qualifier = qualifier
692                 except OverflowError:
693                     # reached overflow condition, break
694                     break
695                 self.assertEqual(e.qualifier, qualifier)
696                 fn(str(e), regex)
697                 qualifier *= 2
698
699     @unittest.skipUnless(IS_PY_3K, "Only supported under Python 3")
700     def testQualifierOverflow(self):
701         """Tests qualifier overflow handling"""
702         acl = posix1e.ACL()
703         e = acl.append()
704         qualifier = sys.maxsize * 2
705         for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
706             e.tag_type = tag
707             with self.assertRaises(OverflowError):
708                 e.qualifier = qualifier
709
710     @unittest.skipUnless(IS_PY_3K, "Only supported under Python 3")
711     def testNegativeQualifier(self):
712         """Tests negative qualifier handling"""
713         # Note: this presumes that uid_t/gid_t in C are unsigned...
714         acl = posix1e.ACL()
715         e = acl.append()
716         for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
717             e.tag_type = tag
718             for qualifier in [-10, -5, -1]:
719                 with self.assertRaises(OverflowError):
720                     e.qualifier = qualifier
721
722     def testInvalidQualifier(self):
723         """Tests invalid qualifier handling"""
724         acl = posix1e.ACL()
725         e = acl.append()
726         def set_qual(x):
727             e.qualifier = x
728         def del_qual():
729             del e.qualifier
730         self.assertRaises(TypeError, set_qual, object())
731         self.assertRaises((TypeError, AttributeError), del_qual)
732
733     def testQualifierOnWrongTag(self):
734         """Tests qualifier setting on wrong tag"""
735         acl = posix1e.ACL()
736         e = acl.append()
737         e.tag_type = posix1e.ACL_OTHER
738         def set_qual(x):
739             e.qualifier = x
740         def get_qual():
741             return e.qualifier
742         self.assertRaises(TypeError, set_qual, 1)
743         self.assertRaises(TypeError, get_qual)
744
745
746     def testTagTypes(self):
747         """Tests tag type correct set/get"""
748         acl = posix1e.ACL()
749         e = acl.append()
750         for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP, posix1e.ACL_USER_OBJ,
751                     posix1e.ACL_GROUP_OBJ, posix1e.ACL_MASK,
752                     posix1e.ACL_OTHER]:
753             e.tag_type = tag
754             self.assertEqual(e.tag_type, tag)
755             # check we can show all tag types without breaking
756             self.assertTrue(str(e))
757
758     def testInvalidTags(self):
759         """Tests tag type incorrect set/get"""
760         acl = posix1e.ACL()
761         e = acl.append()
762         def set_tag(x):
763           e.tag_type = x
764         self.assertRaises(TypeError, set_tag, object())
765         def delete_tag():
766           del e.tag_type
767         # For some reason, PyPy raises AttributeError. Strange...
768         self.assertRaises((TypeError, AttributeError), delete_tag)
769
770         e.tag_type = posix1e.ACL_USER_OBJ
771         tag = max([posix1e.ACL_USER, posix1e.ACL_GROUP, posix1e.ACL_USER_OBJ,
772                    posix1e.ACL_GROUP_OBJ, posix1e.ACL_MASK,
773                    posix1e.ACL_OTHER]) + 1
774         self.assertRaises(EnvironmentError, set_tag, tag)
775         # Check tag is still valid.
776         self.assertEqual(e.tag_type, posix1e.ACL_USER_OBJ)
777
778 if __name__ == "__main__":
779     unittest.main()