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