]> git.k1024.org Git - pylibacl.git/blob - tests/test_acls.py
Switch LoadTests 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 class aclTest:
177     """Support functions ACLs"""
178
179     def setUp(self):
180         """set up function"""
181         self.rmfiles = []
182         self.rmdirs = []
183
184     def tearDown(self):
185         """tear down function"""
186         for fname in self.rmfiles:
187             os.unlink(fname)
188         for dname in self.rmdirs:
189             os.rmdir(dname)
190
191     def _getfile(self):
192         """create a temp file"""
193         fh, fname = tempfile.mkstemp(".test", "xattr-", TEST_DIR)
194         self.rmfiles.append(fname)
195         return fh, fname
196
197     def _getdir(self):
198         """create a temp dir"""
199         dname = tempfile.mkdtemp(".test", "xattr-", TEST_DIR)
200         self.rmdirs.append(dname)
201         return dname
202
203     def _getsymlink(self):
204         """create a symlink"""
205         fh, fname = self._getfile()
206         os.close(fh)
207         os.unlink(fname)
208         os.symlink(fname + ".non-existent", fname)
209         return fname
210
211
212 class TestLoad:
213     """Load/create tests"""
214     def test_from_file(self, testdir):
215         """Test loading ACLs from a file"""
216         _, fname = get_file(testdir)
217         acl1 = posix1e.ACL(file=fname)
218         assert acl1.valid()
219
220     def test_from_dir(self, testdir):
221         """Test loading ACLs from a directory"""
222         with get_dir(testdir) as dname:
223           acl1 = posix1e.ACL(file=dname)
224           acl2 = posix1e.ACL(filedef=dname)
225           assert acl1.valid()
226         # default ACLs might or might not be valid; missing ones are
227         # not valid, so we don't test acl2 for validity
228
229     def test_from_fd(self, testdir):
230         """Test loading ACLs from a file descriptor"""
231         fd, _ = get_file(testdir)
232         acl1 = posix1e.ACL(fd=fd)
233         assert acl1.valid()
234
235     def test_from_empty_invalid(self):
236         """Test creating an empty ACL"""
237         acl1 = posix1e.ACL()
238         assert not acl1.valid()
239
240     def test_from_text(self):
241         """Test creating an ACL from text"""
242         acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
243         assert acl1.valid()
244
245     def test_from_acl(self):
246         """Test creating an ACL from an existing ACL"""
247         acl1 = posix1e.ACL()
248         acl2 = posix1e.ACL(acl=acl1)
249         assert acl1 == acl2
250
251     def test_invalid_creation_params(self, testdir):
252         """Test that creating an ACL from multiple objects fails"""
253         fd, _ = get_file(testdir)
254         with pytest.raises(ValueError):
255           posix1e.ACL(text=BASIC_ACL_TEXT, fd=fd)
256
257     def test_invalid_value_creation(self):
258         """Test that creating an ACL from wrong specification fails"""
259         with pytest.raises(EnvironmentError):
260           posix1e.ACL(text="foobar")
261         with pytest.raises(TypeError):
262           posix1e.ACL(foo="bar")
263
264     def test_double_init(self):
265         acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
266         assert acl1.valid()
267         acl1.__init__(text=BASIC_ACL_TEXT)
268         assert acl1.valid()
269
270 class AclExtensions(aclTest, unittest.TestCase):
271     """ACL extensions checks"""
272
273     @unittest.skipUnless(HAS_ACL_FROM_MODE, "Missing HAS_ACL_FROM_MODE")
274     def testFromMode(self):
275         """Test loading ACLs from an octal mode"""
276         acl1 = posix1e.ACL(mode=M0644)
277         self.assertTrue(acl1.valid(),
278                         "ACL created via octal mode shoule be valid")
279
280     @unittest.skipUnless(HAS_ACL_CHECK, "ACL check not supported")
281     def testAclCheck(self):
282         """Test the acl_check method"""
283         acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
284         self.assertFalse(acl1.check(), "ACL is not valid")
285         acl2 = posix1e.ACL()
286         self.assertTrue(acl2.check(), "Empty ACL should not be valid")
287
288     @unittest.skipUnless(HAS_EXTENDED_CHECK, "Extended ACL check not supported")
289     def testExtended(self):
290         """Test the acl_extended function"""
291         fd, fname = self._getfile()
292         basic_acl = posix1e.ACL(text=BASIC_ACL_TEXT)
293         basic_acl.applyto(fd)
294         for item in fd, fname:
295             self.assertFalse(has_extended(item),
296                              "A simple ACL should not be reported as extended")
297         enhanced_acl = posix1e.ACL(text="u::rw,g::-,o::-,u:root:rw,mask::r")
298         self.assertTrue(enhanced_acl.valid(),
299                         "Failure to build an extended ACL")
300         enhanced_acl.applyto(fd)
301         for item in fd, fname:
302             self.assertTrue(has_extended(item),
303                             "An extended ACL should be reported as such")
304
305     @unittest.skipUnless(HAS_EXTENDED_CHECK, "Extended ACL check not supported")
306     def testExtendedArgHandling(self):
307       self.assertRaises(TypeError, has_extended)
308       self.assertRaises(TypeError, has_extended, object())
309
310     @unittest.skipUnless(HAS_EQUIV_MODE, "equiv_mode not supported")
311     def testEquivMode(self):
312         """Test the equiv_mode function"""
313         if HAS_ACL_FROM_MODE:
314             for mode in M0644, M0755:
315                 acl = posix1e.ACL(mode=mode)
316                 self.assertEqual(acl.equiv_mode(), mode)
317         acl = posix1e.ACL(text="u::rw,g::r,o::r")
318         self.assertEqual(acl.equiv_mode(), M0644)
319         acl = posix1e.ACL(text="u::rx,g::-,o::-")
320         self.assertEqual(acl.equiv_mode(), M0500)
321
322     @unittest.skipUnless(HAS_ACL_CHECK, "ACL check not supported")
323     def testToAnyText(self):
324         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
325         self.assertIn(encode("u::"),
326                           acl.to_any_text(options=posix1e.TEXT_ABBREVIATE))
327         self.assertIn(encode("user::"), acl.to_any_text())
328
329     @unittest.skipUnless(HAS_ACL_CHECK, "ACL check not supported")
330     def testToAnyTextWrongArgs(self):
331         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
332         self.assertRaises(TypeError, acl.to_any_text, foo="bar")
333
334
335     @unittest.skipUnless(HAS_ACL_CHECK, "ACL check not supported")
336     def testRichCompare(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         self.assertEqual(acl1, acl2)
341         self.assertNotEqual(acl1, acl3)
342         self.assertRaises(TypeError, operator.lt, acl1, acl2)
343         self.assertRaises(TypeError, operator.ge, acl1, acl3)
344         self.assertTrue(acl1 != True)
345         self.assertFalse(acl1 == 1)
346         self.assertRaises(TypeError, operator.gt, acl1, True)
347
348     @unittest.skipUnless(hasattr(posix1e.ACL, "__cmp__"), "__cmp__ is missing")
349     @unittest.skipUnless(__pypy__ is None, "Disabled under pypy")
350     def testCmp(self):
351         acl1 = posix1e.ACL()
352         self.assertRaises(TypeError, acl1.__cmp__, acl1)
353
354     def testApplyToWithWrongObject(self):
355         acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
356         self.assertTrue(acl1.valid())
357         self.assertRaises(TypeError, acl1.applyto, object())
358         self.assertRaises(TypeError, acl1.applyto, object(), object())
359
360     @unittest.skipUnless(HAS_ACL_ENTRY, "ACL entries not supported")
361     def testAclIterator(self):
362         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
363         #self.assertEqual(len(acl), 3)
364         for entry in acl:
365             self.assertIs(entry.parent, acl)
366
367
368 class WriteTests(aclTest, unittest.TestCase):
369     """Write tests"""
370
371     def testDeleteDefault(self):
372         """Test removing the default ACL"""
373         dname = self._getdir()
374         posix1e.delete_default(dname)
375
376     @unittest.skipUnless(__pypy__ is None, "Disabled under pypy")
377     def testDeleteDefaultWrongArg(self):
378         self.assertRaises(TypeError, posix1e.delete_default, object())
379
380     def testReapply(self):
381         """Test re-applying an ACL"""
382         fd, fname = self._getfile()
383         acl1 = posix1e.ACL(fd=fd)
384         acl1.applyto(fd)
385         acl1.applyto(fname)
386         dname = self._getdir()
387         acl2 = posix1e.ACL(file=fname)
388         acl2.applyto(dname)
389
390
391 @unittest.skipUnless(HAS_ACL_ENTRY, "ACL entries not supported")
392 class ModificationTests(aclTest, unittest.TestCase):
393     """ACL modification tests"""
394
395     def checkRef(self, obj):
396         """Checks if a given obj has a 'sane' refcount"""
397         if platform.python_implementation() == "PyPy":
398             return
399         ref_cnt = sys.getrefcount(obj)
400         # FIXME: hardcoded value for the max ref count... but I've
401         # seen it overflow on bad reference counting, so it's better
402         # to be safe
403         if ref_cnt < 2 or ref_cnt > 1024:
404             self.fail("Wrong reference count, expected 2-1024 and got %d" %
405                       ref_cnt)
406
407     def testStr(self):
408         """Test str() of an ACL."""
409         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
410         str_acl = str(acl)
411         self.checkRef(str_acl)
412
413     def testAppend(self):
414         """Test append a new Entry to the ACL"""
415         acl = posix1e.ACL()
416         e = acl.append()
417         e.tag_type = posix1e.ACL_OTHER
418         ignore_ioerror(errno.EINVAL, acl.calc_mask)
419         str_format = str(e)
420         self.checkRef(str_format)
421         e2 = acl.append(e)
422         ignore_ioerror(errno.EINVAL, acl.calc_mask)
423         self.assertFalse(acl.valid())
424
425     def testWrongAppend(self):
426         """Test append a new Entry to the ACL based on wrong object type"""
427         acl = posix1e.ACL()
428         self.assertRaises(TypeError, acl.append, object())
429
430     def testEntryCreation(self):
431         acl = posix1e.ACL()
432         e = posix1e.Entry(acl)
433         ignore_ioerror(errno.EINVAL, acl.calc_mask)
434         str_format = str(e)
435         self.checkRef(str_format)
436
437     def testEntryFailedCreation(self):
438         # Checks for partial initialisation and deletion on error
439         # path.
440         self.assertRaises(TypeError, posix1e.Entry, object())
441
442     def testDelete(self):
443         """Test delete Entry from the ACL"""
444         acl = posix1e.ACL()
445         e = acl.append()
446         e.tag_type = posix1e.ACL_OTHER
447         ignore_ioerror(errno.EINVAL, acl.calc_mask)
448         acl.delete_entry(e)
449         ignore_ioerror(errno.EINVAL, acl.calc_mask)
450
451     def testDoubleDelete(self):
452         """Test delete Entry from the ACL"""
453         # This is not entirely valid/correct, since the entry object
454         # itself is invalid after the first deletion, so we're
455         # actually testing deleting an invalid object, not a
456         # non-existing entry...
457         acl = posix1e.ACL()
458         e = acl.append()
459         e.tag_type = posix1e.ACL_OTHER
460         ignore_ioerror(errno.EINVAL, acl.calc_mask)
461         acl.delete_entry(e)
462         ignore_ioerror(errno.EINVAL, acl.calc_mask)
463         self.assertRaises(EnvironmentError, acl.delete_entry, e)
464
465     # This currently fails as this deletion seems to be accepted :/
466     @unittest.skip("Entry deletion is unreliable")
467     def testDeleteInvalidEntry(self):
468         """Test delete foreign Entry from the ACL"""
469         acl1 = posix1e.ACL()
470         acl2 = posix1e.ACL()
471         e = acl1.append()
472         e.tag_type = posix1e.ACL_OTHER
473         ignore_ioerror(errno.EINVAL, acl1.calc_mask)
474         self.assertRaises(EnvironmentError, acl2.delete_entry, e)
475
476     def testDeleteInvalidObject(self):
477         """Test delete a non-Entry from the ACL"""
478         acl = posix1e.ACL()
479         self.assertRaises(TypeError, acl.delete_entry, object())
480
481     def testDoubleEntries(self):
482         """Test double entries"""
483         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
484         self.assertTrue(acl.valid(), "ACL is not valid")
485         for tag_type in (posix1e.ACL_USER_OBJ, posix1e.ACL_GROUP_OBJ,
486                          posix1e.ACL_OTHER):
487             e = acl.append()
488             e.tag_type = tag_type
489             e.permset.clear()
490             self.assertFalse(acl.valid(),
491                 "ACL containing duplicate entries"
492                 " should not be valid")
493             acl.delete_entry(e)
494
495     def testMultipleGoodEntries(self):
496         """Test multiple valid entries"""
497         acl = posix1e.ACL(text=BASIC_ACL_TEXT)
498         self.assertTrue(acl.valid(), "ACL is not valid")
499         for tag_type in (posix1e.ACL_USER,
500                          posix1e.ACL_GROUP):
501             for obj_id in range(5):
502                 e = acl.append()
503                 e.tag_type = tag_type
504                 e.qualifier = obj_id
505                 e.permset.clear()
506                 acl.calc_mask()
507                 self.assertTrue(acl.valid(),
508                     "ACL should be able to hold multiple"
509                     " user/group entries")
510
511     def testMultipleBadEntries(self):
512         """Test multiple invalid entries"""
513         for tag_type in (posix1e.ACL_USER,
514                          posix1e.ACL_GROUP):
515             acl = posix1e.ACL(text=BASIC_ACL_TEXT)
516             self.assertTrue(acl.valid(), "ACL built from standard description"
517                                          " should be valid")
518             e1 = acl.append()
519             e1.tag_type = tag_type
520             e1.qualifier = 0
521             e1.permset.clear()
522             acl.calc_mask()
523             self.assertTrue(acl.valid(), "ACL should be able to add a"
524                 " user/group entry")
525             e2 = acl.append()
526             e2.tag_type = tag_type
527             e2.qualifier = 0
528             e2.permset.clear()
529             ignore_ioerror(errno.EINVAL, acl.calc_mask)
530             self.assertFalse(acl.valid(), "ACL should not validate when"
531                 " containing two duplicate entries")
532             acl.delete_entry(e1)
533             # FreeBSD trips over itself here and can't delete the
534             # entry, even though it still exists.
535             ignore_ioerror(errno.EINVAL, acl.delete_entry, e2)
536
537     def testCopy(self):
538         acl = ACL()
539         e1 = acl.append()
540         e1.tag_type = ACL_USER
541         p1 = e1.permset
542         p1.clear()
543         p1.read = True
544         p1.write = True
545         e2 = acl.append()
546         e2.tag_type = ACL_GROUP
547         p2 = e2.permset
548         p2.clear()
549         p2.read = True
550         self.assertFalse(p2.write)
551         e2.copy(e1)
552         self.assertTrue(p2.write)
553         self.assertEqual(e1.tag_type, e2.tag_type)
554
555     def testCopyWrongArg(self):
556         acl = ACL()
557         e = acl.append()
558         self.assertRaises(TypeError, e.copy, object())
559
560     def testSetPermset(self):
561         acl = ACL()
562         e1 = acl.append()
563         e1.tag_type = ACL_USER
564         p1 = e1.permset
565         p1.clear()
566         p1.read = True
567         p1.write = True
568         e2 = acl.append()
569         e2.tag_type = ACL_GROUP
570         p2 = e2.permset
571         p2.clear()
572         p2.read = True
573         self.assertFalse(p2.write)
574         e2.permset = p1
575         self.assertTrue(e2.permset.write)
576         self.assertEqual(e2.tag_type, ACL_GROUP)
577
578     def testSetPermsetWrongArg(self):
579         acl = ACL()
580         e = acl.append()
581         def setter(v):
582             e.permset = v
583         self.assertRaises(TypeError, setter, object())
584
585     def testPermsetCreation(self):
586         acl = ACL()
587         e = acl.append()
588         p1 = e.permset
589         p2 = Permset(e)
590         #self.assertEqual(p1, p2)
591
592     def testPermsetCreationWrongArg(self):
593         self.assertRaises(TypeError, Permset, object())
594
595     def testPermset(self):
596         """Test permissions"""
597         acl = posix1e.ACL()
598         e = acl.append()
599         ps = e.permset
600         ps.clear()
601         str_ps = str(ps)
602         self.checkRef(str_ps)
603         for perm in PERMSETS:
604             str_ps = str(ps)
605             txt = PERMSETS[perm][0]
606             self.checkRef(str_ps)
607             self.assertFalse(ps.test(perm), "Empty permission set should not"
608                 " have permission '%s'" % txt)
609             ps.add(perm)
610             self.assertTrue(ps.test(perm), "Permission '%s' should exist"
611                 " after addition" % txt)
612             str_ps = str(ps)
613             self.checkRef(str_ps)
614             ps.delete(perm)
615             self.assertFalse(ps.test(perm), "Permission '%s' should not exist"
616                 " after deletion" % txt)
617
618     def testPermsetViaAccessors(self):
619         """Test permissions"""
620         acl = posix1e.ACL()
621         e = acl.append()
622         ps = e.permset
623         ps.clear()
624         str_ps = str(ps)
625         self.checkRef(str_ps)
626         def getter(perm):
627             return PERMSETS[perm][1].__get__(ps)
628         def setter(parm, value):
629             return PERMSETS[perm][1].__set__(ps, value)
630         for perm in PERMSETS:
631             str_ps = str(ps)
632             self.checkRef(str_ps)
633             txt = PERMSETS[perm][0]
634             self.assertFalse(getter(perm), "Empty permission set should not"
635                 " have permission '%s'" % txt)
636             setter(perm, True)
637             self.assertTrue(ps.test(perm), "Permission '%s' should exist"
638                 " after addition" % txt)
639             self.assertTrue(getter(perm), "Permission '%s' should exist"
640                 " after addition" % txt)
641             str_ps = str(ps)
642             self.checkRef(str_ps)
643             setter(perm, False)
644             self.assertFalse(ps.test(perm), "Permission '%s' should not exist"
645                 " after deletion" % txt)
646             self.assertFalse(getter(perm), "Permission '%s' should not exist"
647                 " after deletion" % txt)
648
649     def testPermsetInvalidType(self):
650         acl = posix1e.ACL()
651         e = acl.append()
652         ps = e.permset
653         ps.clear()
654         def setter():
655             ps.write = object()
656         self.assertRaises(TypeError, ps.add, "foobar")
657         self.assertRaises(TypeError, ps.delete, "foobar")
658         self.assertRaises(TypeError, ps.test, "foobar")
659         self.assertRaises(ValueError, setter)
660
661     @unittest.skipUnless(IS_PY_3K, "Only supported under Python 3")
662     def testQualifierValues(self):
663         """Tests qualifier correct store/retrieval"""
664         acl = posix1e.ACL()
665         e = acl.append()
666         # work around deprecation warnings
667         if hasattr(self, 'assertRegex'):
668             fn = self.assertRegex
669         else:
670             fn = self.assertRegexpMatches
671         for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
672             qualifier = 1
673             e.tag_type = tag
674             while True:
675                 if tag == posix1e.ACL_USER:
676                     regex = re.compile("user with uid %d" % qualifier)
677                 else:
678                     regex = re.compile("group with gid %d" % qualifier)
679                 try:
680                     e.qualifier = qualifier
681                 except OverflowError:
682                     # reached overflow condition, break
683                     break
684                 self.assertEqual(e.qualifier, qualifier)
685                 fn(str(e), regex)
686                 qualifier *= 2
687
688     @unittest.skipUnless(IS_PY_3K, "Only supported under Python 3")
689     def testQualifierOverflow(self):
690         """Tests qualifier overflow handling"""
691         acl = posix1e.ACL()
692         e = acl.append()
693         qualifier = sys.maxsize * 2
694         for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
695             e.tag_type = tag
696             with self.assertRaises(OverflowError):
697                 e.qualifier = qualifier
698
699     @unittest.skipUnless(IS_PY_3K, "Only supported under Python 3")
700     def testNegativeQualifier(self):
701         """Tests negative qualifier handling"""
702         # Note: this presumes that uid_t/gid_t in C are unsigned...
703         acl = posix1e.ACL()
704         e = acl.append()
705         for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
706             e.tag_type = tag
707             for qualifier in [-10, -5, -1]:
708                 with self.assertRaises(OverflowError):
709                     e.qualifier = qualifier
710
711     def testInvalidQualifier(self):
712         """Tests invalid qualifier handling"""
713         acl = posix1e.ACL()
714         e = acl.append()
715         def set_qual(x):
716             e.qualifier = x
717         def del_qual():
718             del e.qualifier
719         self.assertRaises(TypeError, set_qual, object())
720         self.assertRaises((TypeError, AttributeError), del_qual)
721
722     def testQualifierOnWrongTag(self):
723         """Tests qualifier setting on wrong tag"""
724         acl = posix1e.ACL()
725         e = acl.append()
726         e.tag_type = posix1e.ACL_OTHER
727         def set_qual(x):
728             e.qualifier = x
729         def get_qual():
730             return e.qualifier
731         self.assertRaises(TypeError, set_qual, 1)
732         self.assertRaises(TypeError, get_qual)
733
734
735     def testTagTypes(self):
736         """Tests tag type correct set/get"""
737         acl = posix1e.ACL()
738         e = acl.append()
739         for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP, posix1e.ACL_USER_OBJ,
740                     posix1e.ACL_GROUP_OBJ, posix1e.ACL_MASK,
741                     posix1e.ACL_OTHER]:
742             e.tag_type = tag
743             self.assertEqual(e.tag_type, tag)
744             # check we can show all tag types without breaking
745             self.assertTrue(str(e))
746
747     def testInvalidTags(self):
748         """Tests tag type incorrect set/get"""
749         acl = posix1e.ACL()
750         e = acl.append()
751         def set_tag(x):
752           e.tag_type = x
753         self.assertRaises(TypeError, set_tag, object())
754         def delete_tag():
755           del e.tag_type
756         # For some reason, PyPy raises AttributeError. Strange...
757         self.assertRaises((TypeError, AttributeError), delete_tag)
758
759         e.tag_type = posix1e.ACL_USER_OBJ
760         tag = max([posix1e.ACL_USER, posix1e.ACL_GROUP, posix1e.ACL_USER_OBJ,
761                    posix1e.ACL_GROUP_OBJ, posix1e.ACL_MASK,
762                    posix1e.ACL_OTHER]) + 1
763         self.assertRaises(EnvironmentError, set_tag, tag)
764         # Check tag is still valid.
765         self.assertEqual(e.tag_type, posix1e.ACL_USER_OBJ)
766
767 if __name__ == "__main__":
768     unittest.main()