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