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