4 """Unittests for the posix1e module"""
6 # Copyright (C) 2002-2009, 2012, 2014, 2015 Iustin Pop <iustin@k1024.org>
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.
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.
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
41 TEST_DIR = os.environ.get("TEST_DIR", ".")
43 BASIC_ACL_TEXT = "u::rw,g::r,o::-"
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
51 # Permset permission information
53 posix1e.ACL_READ: ("read", posix1e.Permset.read),
54 posix1e.ACL_WRITE: ("write", posix1e.Permset.write),
55 posix1e.ACL_EXECUTE: ("execute", posix1e.Permset.execute),
59 # Check if running under Python 3
60 IS_PY_3K = sys.hexversion >= 0x03000000
63 """Wrapper to skip a test for python 2.6"""
64 new_fn = lambda x: None
65 new_fn.__doc__ = "SKIPPED %s" % fn.__doc__
69 """Ignore failures in a test for python 2.6"""
73 except AssertionError:
74 print ("Ignoring failure")
77 def has_ext(extension):
78 """Decorator to skip tests based on platform support"""
80 if hasattr(unittest, 'skip'):
81 return unittest.skip("Precondition failed")
85 return lambda func: func
88 return getattr(unittest, 'expectedFailure', _ignore_test)
90 def ignore_ioerror(errnum, fn, *args, **kwargs):
91 """Call a function while ignoring some IOErrors.
93 This is needed as some OSes (e.g. FreeBSD) return failure (EINVAL)
94 when doing certain operations on an invalid ACL.
100 err = sys.exc_info()[1]
101 if err.errno == errnum:
106 """Encode a string if needed (under Python 3)"""
114 """Support functions ACLs"""
117 """set up function"""
122 """tear down function"""
123 for fname in self.rmfiles:
125 for dname in self.rmdirs:
129 """create a temp file"""
130 fh, fname = tempfile.mkstemp(".test", "xattr-", TEST_DIR)
131 self.rmfiles.append(fname)
135 """create a temp dir"""
136 dname = tempfile.mkdtemp(".test", "xattr-", TEST_DIR)
137 self.rmdirs.append(dname)
140 def _getsymlink(self):
141 """create a symlink"""
142 fh, fname = self._getfile()
145 os.symlink(fname + ".non-existent", fname)
149 class LoadTests(aclTest, unittest.TestCase):
150 """Load/create tests"""
151 def testFromFile(self):
152 """Test loading ACLs from a file"""
153 _, fname = self._getfile()
154 acl1 = posix1e.ACL(file=fname)
155 self.assertTrue(acl1.valid(), "ACL read from file should be valid")
157 def testFromDir(self):
158 """Test loading ACLs from a directory"""
159 dname = self._getdir()
160 acl1 = posix1e.ACL(file=dname)
161 acl2 = posix1e.ACL(filedef=dname)
162 self.assertTrue(acl1.valid(),
163 "ACL read from directory should be valid")
164 # default ACLs might or might not be valid; missing ones are
165 # not valid, so we don't test acl2 for validity
167 def testFromFd(self):
168 """Test loading ACLs from a file descriptor"""
169 fd, _ = self._getfile()
170 acl1 = posix1e.ACL(fd=fd)
171 self.assertTrue(acl1.valid(), "ACL read from fd should be valid")
173 def testFromEmpty(self):
174 """Test creating an empty ACL"""
176 self.assertFalse(acl1.valid(), "Empty ACL should not be valid")
178 def testFromText(self):
179 """Test creating an ACL from text"""
180 acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
181 self.assertTrue(acl1.valid(),
182 "ACL based on standard description should be valid")
184 def testFromACL(self):
185 """Test creating an ACL from an existing ACL"""
187 acl2 = posix1e.ACL(acl=acl1)
189 def testInvalidCreationParams(self):
190 """Test that creating an ACL from multiple objects fails"""
191 fd, _ = self._getfile()
192 self.assertRaises(ValueError, posix1e.ACL, text=BASIC_ACL_TEXT, fd=fd)
194 def testInvalidValueCreation(self):
195 """Test that creating an ACL from wrong specification fails"""
196 self.assertRaises(EnvironmentError, posix1e.ACL, text="foobar")
197 self.assertRaises(TypeError, posix1e.ACL, foo="bar")
199 def testDoubleInit(self):
200 acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
201 self.assertTrue(acl1.valid())
202 acl1.__init__(text=BASIC_ACL_TEXT)
203 self.assertTrue(acl1.valid())
205 class AclExtensions(aclTest, unittest.TestCase):
206 """ACL extensions checks"""
208 @has_ext(HAS_ACL_FROM_MODE)
209 def testFromMode(self):
210 """Test loading ACLs from an octal mode"""
211 acl1 = posix1e.ACL(mode=M0644)
212 self.assertTrue(acl1.valid(),
213 "ACL created via octal mode shoule be valid")
215 @has_ext(HAS_ACL_CHECK)
216 def testAclCheck(self):
217 """Test the acl_check method"""
218 acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
219 self.assertFalse(acl1.check(), "ACL is not valid")
221 self.assertTrue(acl2.check(), "Empty ACL should not be valid")
223 @has_ext(HAS_EXTENDED_CHECK)
224 def testExtended(self):
225 """Test the acl_extended function"""
226 fd, fname = self._getfile()
227 basic_acl = posix1e.ACL(text=BASIC_ACL_TEXT)
228 basic_acl.applyto(fd)
229 for item in fd, fname:
230 self.assertFalse(has_extended(item),
231 "A simple ACL should not be reported as extended")
232 enhanced_acl = posix1e.ACL(text="u::rw,g::-,o::-,u:root:rw,mask::r")
233 self.assertTrue(enhanced_acl.valid(),
234 "Failure to build an extended ACL")
235 enhanced_acl.applyto(fd)
236 for item in fd, fname:
237 self.assertTrue(has_extended(item),
238 "An extended ACL should be reported as such")
240 @has_ext(HAS_EXTENDED_CHECK)
241 def testExtendedArgHandling(self):
242 self.assertRaises(TypeError, has_extended)
243 self.assertRaises(TypeError, has_extended, object())
245 @has_ext(HAS_EQUIV_MODE)
246 def testEquivMode(self):
247 """Test the equiv_mode function"""
248 if HAS_ACL_FROM_MODE:
249 for mode in M0644, M0755:
250 acl = posix1e.ACL(mode=mode)
251 self.assertEqual(acl.equiv_mode(), mode)
252 acl = posix1e.ACL(text="u::rw,g::r,o::r")
253 self.assertEqual(acl.equiv_mode(), M0644)
254 acl = posix1e.ACL(text="u::rx,g::-,o::-")
255 self.assertEqual(acl.equiv_mode(), M0500)
257 @has_ext(HAS_ACL_CHECK)
258 def testToAnyText(self):
259 acl = posix1e.ACL(text=BASIC_ACL_TEXT)
260 self.assertIn(encode("u::"),
261 acl.to_any_text(options=posix1e.TEXT_ABBREVIATE))
262 self.assertIn(encode("user::"), acl.to_any_text())
264 @has_ext(HAS_ACL_CHECK)
265 def testToAnyTextWrongArgs(self):
266 acl = posix1e.ACL(text=BASIC_ACL_TEXT)
267 self.assertRaises(TypeError, acl.to_any_text, foo="bar")
270 @has_ext(HAS_ACL_CHECK)
271 def testRichCompare(self):
272 acl1 = posix1e.ACL(text="u::rw,g::r,o::r")
273 acl2 = posix1e.ACL(acl=acl1)
274 acl3 = posix1e.ACL(text="u::rw,g::rw,o::r")
275 self.assertEqual(acl1, acl2)
276 self.assertNotEqual(acl1, acl3)
277 self.assertRaises(TypeError, operator.lt, acl1, acl2)
278 self.assertRaises(TypeError, operator.ge, acl1, acl3)
279 self.assertTrue(acl1 != True)
280 self.assertFalse(acl1 == 1)
281 self.assertRaises(TypeError, operator.gt, acl1, True)
283 @has_ext(hasattr(posix1e.ACL, "__cmp__") and __pypy__ is None)
286 self.assertRaises(TypeError, acl1.__cmp__, acl1)
288 def testApplyToWithWrongObject(self):
289 acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
290 self.assertTrue(acl1.valid())
291 self.assertRaises(TypeError, acl1.applyto, object())
292 self.assertRaises(TypeError, acl1.applyto, object(), object())
294 @has_ext(HAS_ACL_ENTRY)
295 def testAclIterator(self):
296 acl = posix1e.ACL(text=BASIC_ACL_TEXT)
297 #self.assertEqual(len(acl), 3)
299 self.assertIs(entry.parent, acl)
302 class WriteTests(aclTest, unittest.TestCase):
305 def testDeleteDefault(self):
306 """Test removing the default ACL"""
307 dname = self._getdir()
308 posix1e.delete_default(dname)
310 @has_ext(__pypy__ is None)
311 def testDeleteDefaultWrongArg(self):
312 self.assertRaises(TypeError, posix1e.delete_default, object())
314 def testReapply(self):
315 """Test re-applying an ACL"""
316 fd, fname = self._getfile()
317 acl1 = posix1e.ACL(fd=fd)
320 dname = self._getdir()
321 acl2 = posix1e.ACL(file=fname)
325 class ModificationTests(aclTest, unittest.TestCase):
326 """ACL modification tests"""
328 def checkRef(self, obj):
329 """Checks if a given obj has a 'sane' refcount"""
330 if platform.python_implementation() == "PyPy":
332 ref_cnt = sys.getrefcount(obj)
333 # FIXME: hardcoded value for the max ref count... but I've
334 # seen it overflow on bad reference counting, so it's better
336 if ref_cnt < 2 or ref_cnt > 1024:
337 self.fail("Wrong reference count, expected 2-1024 and got %d" %
341 """Test str() of an ACL."""
342 acl = posix1e.ACL(text=BASIC_ACL_TEXT)
344 self.checkRef(str_acl)
346 @has_ext(HAS_ACL_ENTRY)
347 def testAppend(self):
348 """Test append a new Entry to the ACL"""
351 e.tag_type = posix1e.ACL_OTHER
352 ignore_ioerror(errno.EINVAL, acl.calc_mask)
354 self.checkRef(str_format)
356 ignore_ioerror(errno.EINVAL, acl.calc_mask)
357 self.assertFalse(acl.valid())
359 @has_ext(HAS_ACL_ENTRY)
360 def testWrongAppend(self):
361 """Test append a new Entry to the ACL based on wrong object type"""
363 self.assertRaises(TypeError, acl.append, object())
365 @has_ext(HAS_ACL_ENTRY)
366 def testEntryCreation(self):
368 e = posix1e.Entry(acl)
369 ignore_ioerror(errno.EINVAL, acl.calc_mask)
371 self.checkRef(str_format)
373 @has_ext(HAS_ACL_ENTRY)
374 def testEntryFailedCreation(self):
375 # Checks for partial initialisation and deletion on error
377 self.assertRaises(TypeError, posix1e.Entry, object())
379 @has_ext(HAS_ACL_ENTRY)
380 def testDelete(self):
381 """Test delete Entry from the ACL"""
384 e.tag_type = posix1e.ACL_OTHER
385 ignore_ioerror(errno.EINVAL, acl.calc_mask)
387 ignore_ioerror(errno.EINVAL, acl.calc_mask)
389 @has_ext(HAS_ACL_ENTRY)
390 def testDoubleDelete(self):
391 """Test delete Entry from the ACL"""
392 # This is not entirely valid/correct, since the entry object
393 # itself is invalid after the first deletion, so we're
394 # actually testing deleting an invalid object, not a
395 # non-existing entry...
398 e.tag_type = posix1e.ACL_OTHER
399 ignore_ioerror(errno.EINVAL, acl.calc_mask)
401 ignore_ioerror(errno.EINVAL, acl.calc_mask)
402 self.assertRaises(EnvironmentError, acl.delete_entry, e)
404 # This currently fails as this deletion seems to be accepted :/
405 @has_ext(HAS_ACL_ENTRY and False)
406 def testDeleteInvalidEntry(self):
407 """Test delete foreign Entry from the ACL"""
411 e.tag_type = posix1e.ACL_OTHER
412 ignore_ioerror(errno.EINVAL, acl1.calc_mask)
413 self.assertRaises(EnvironmentError, acl2.delete_entry, e)
415 @has_ext(HAS_ACL_ENTRY)
416 def testDeleteInvalidObject(self):
417 """Test delete a non-Entry from the ACL"""
419 self.assertRaises(TypeError, acl.delete_entry, object())
421 @has_ext(HAS_ACL_ENTRY)
422 def testDoubleEntries(self):
423 """Test double entries"""
424 acl = posix1e.ACL(text=BASIC_ACL_TEXT)
425 self.assertTrue(acl.valid(), "ACL is not valid")
426 for tag_type in (posix1e.ACL_USER_OBJ, posix1e.ACL_GROUP_OBJ,
429 e.tag_type = tag_type
431 self.assertFalse(acl.valid(),
432 "ACL containing duplicate entries"
433 " should not be valid")
436 @has_ext(HAS_ACL_ENTRY)
437 def testMultipleGoodEntries(self):
438 """Test multiple valid entries"""
439 acl = posix1e.ACL(text=BASIC_ACL_TEXT)
440 self.assertTrue(acl.valid(), "ACL is not valid")
441 for tag_type in (posix1e.ACL_USER,
443 for obj_id in range(5):
445 e.tag_type = tag_type
449 self.assertTrue(acl.valid(),
450 "ACL should be able to hold multiple"
451 " user/group entries")
453 @has_ext(HAS_ACL_ENTRY)
454 def testMultipleBadEntries(self):
455 """Test multiple invalid entries"""
456 for tag_type in (posix1e.ACL_USER,
458 acl = posix1e.ACL(text=BASIC_ACL_TEXT)
459 self.assertTrue(acl.valid(), "ACL built from standard description"
462 e1.tag_type = tag_type
466 self.assertTrue(acl.valid(), "ACL should be able to add a"
469 e2.tag_type = tag_type
472 ignore_ioerror(errno.EINVAL, acl.calc_mask)
473 self.assertFalse(acl.valid(), "ACL should not validate when"
474 " containing two duplicate entries")
476 # FreeBSD trips over itself here and can't delete the
477 # entry, even though it still exists.
478 ignore_ioerror(errno.EINVAL, acl.delete_entry, e2)
480 @has_ext(HAS_ACL_ENTRY)
484 e1.tag_type = ACL_USER
490 e2.tag_type = ACL_GROUP
494 self.assertFalse(p2.write)
496 self.assertTrue(p2.write)
497 self.assertEqual(e1.tag_type, e2.tag_type)
499 @has_ext(HAS_ACL_ENTRY)
500 def testCopyWrongArg(self):
503 self.assertRaises(TypeError, e.copy, object())
505 @has_ext(HAS_ACL_ENTRY)
506 def testSetPermset(self):
509 e1.tag_type = ACL_USER
515 e2.tag_type = ACL_GROUP
519 self.assertFalse(p2.write)
521 self.assertTrue(e2.permset.write)
522 self.assertEqual(e2.tag_type, ACL_GROUP)
524 @has_ext(HAS_ACL_ENTRY)
525 def testSetPermsetWrongArg(self):
530 self.assertRaises(TypeError, setter, object())
532 @has_ext(HAS_ACL_ENTRY)
533 def testPermsetCreation(self):
538 #self.assertEqual(p1, p2)
540 @has_ext(HAS_ACL_ENTRY)
541 def testPermsetCreationWrongArg(self):
542 self.assertRaises(TypeError, Permset, object())
544 @has_ext(HAS_ACL_ENTRY)
545 def testPermset(self):
546 """Test permissions"""
552 self.checkRef(str_ps)
553 for perm in PERMSETS:
555 txt = PERMSETS[perm][0]
556 self.checkRef(str_ps)
557 self.assertFalse(ps.test(perm), "Empty permission set should not"
558 " have permission '%s'" % txt)
560 self.assertTrue(ps.test(perm), "Permission '%s' should exist"
561 " after addition" % txt)
563 self.checkRef(str_ps)
565 self.assertFalse(ps.test(perm), "Permission '%s' should not exist"
566 " after deletion" % txt)
568 @has_ext(HAS_ACL_ENTRY)
569 def testPermsetViaAccessors(self):
570 """Test permissions"""
576 self.checkRef(str_ps)
578 return PERMSETS[perm][1].__get__(ps)
579 def setter(parm, value):
580 return PERMSETS[perm][1].__set__(ps, value)
581 for perm in PERMSETS:
583 self.checkRef(str_ps)
584 txt = PERMSETS[perm][0]
585 self.assertFalse(getter(perm), "Empty permission set should not"
586 " have permission '%s'" % txt)
588 self.assertTrue(ps.test(perm), "Permission '%s' should exist"
589 " after addition" % txt)
590 self.assertTrue(getter(perm), "Permission '%s' should exist"
591 " after addition" % txt)
593 self.checkRef(str_ps)
595 self.assertFalse(ps.test(perm), "Permission '%s' should not exist"
596 " after deletion" % txt)
597 self.assertFalse(getter(perm), "Permission '%s' should not exist"
598 " after deletion" % txt)
600 @has_ext(HAS_ACL_ENTRY)
601 def testPermsetInvalidType(self):
608 self.assertRaises(TypeError, ps.add, "foobar")
609 self.assertRaises(TypeError, ps.delete, "foobar")
610 self.assertRaises(TypeError, ps.test, "foobar")
611 self.assertRaises(ValueError, setter)
613 @has_ext(HAS_ACL_ENTRY and IS_PY_3K)
614 def testQualifierValues(self):
615 """Tests qualifier correct store/retrieval"""
618 # work around deprecation warnings
619 if hasattr(self, 'assertRegex'):
620 fn = self.assertRegex
622 fn = self.assertRegexpMatches
623 for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
627 if tag == posix1e.ACL_USER:
628 regex = re.compile("user with uid %d" % qualifier)
630 regex = re.compile("group with gid %d" % qualifier)
632 e.qualifier = qualifier
633 except OverflowError:
634 # reached overflow condition, break
636 self.assertEqual(e.qualifier, qualifier)
640 @has_ext(HAS_ACL_ENTRY and IS_PY_3K)
641 def testQualifierOverflow(self):
642 """Tests qualifier overflow handling"""
645 qualifier = sys.maxsize * 2
646 for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
648 with self.assertRaises(OverflowError):
649 e.qualifier = qualifier
651 @has_ext(HAS_ACL_ENTRY and IS_PY_3K)
652 def testNegativeQualifier(self):
653 """Tests negative qualifier handling"""
654 # Note: this presumes that uid_t/gid_t in C are unsigned...
657 for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
659 for qualifier in [-10, -5, -1]:
660 with self.assertRaises(OverflowError):
661 e.qualifier = qualifier
663 @has_ext(HAS_ACL_ENTRY)
664 def testInvalidQualifier(self):
665 """Tests invalid qualifier handling"""
672 self.assertRaises(TypeError, set_qual, object())
673 self.assertRaises((TypeError, AttributeError), del_qual)
675 @has_ext(HAS_ACL_ENTRY)
676 def testQualifierOnWrongTag(self):
677 """Tests qualifier setting on wrong tag"""
680 e.tag_type = posix1e.ACL_OTHER
685 self.assertRaises(TypeError, set_qual, 1)
686 self.assertRaises(TypeError, get_qual)
689 @has_ext(HAS_ACL_ENTRY)
690 def testTagTypes(self):
691 """Tests tag type correct set/get"""
694 for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP, posix1e.ACL_USER_OBJ,
695 posix1e.ACL_GROUP_OBJ, posix1e.ACL_MASK,
698 self.assertEqual(e.tag_type, tag)
699 # check we can show all tag types without breaking
700 self.assertTrue(str(e))
702 @has_ext(HAS_ACL_ENTRY)
703 def testInvalidTags(self):
704 """Tests tag type incorrect set/get"""
709 self.assertRaises(TypeError, set_tag, object())
712 # For some reason, PyPy raises AttributeError. Strange...
713 self.assertRaises((TypeError, AttributeError), delete_tag)
715 e.tag_type = posix1e.ACL_USER_OBJ
716 tag = max([posix1e.ACL_USER, posix1e.ACL_GROUP, posix1e.ACL_USER_OBJ,
717 posix1e.ACL_GROUP_OBJ, posix1e.ACL_MASK,
718 posix1e.ACL_OTHER]) + 1
719 self.assertRaises(EnvironmentError, set_tag, tag)
720 # Check tag is still valid.
721 self.assertEqual(e.tag_type, posix1e.ACL_USER_OBJ)
723 if __name__ == "__main__":